Fixed bad fallback when hitting only a secondary release type as used by picard, fixes #766
This commit is contained in:
@@ -253,11 +253,7 @@ namespace lms::ui
|
|||||||
{
|
{
|
||||||
Wt::WTemplate* releaseContainer{ releaseContainers->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artist.template.release-container")) };
|
Wt::WTemplate* releaseContainer{ releaseContainers->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artist.template.release-container")) };
|
||||||
|
|
||||||
if (releaseType.primaryType || !releaseType.customTypes.empty())
|
releaseContainer->bindString("release-type", releaseHelpers::buildReleaseTypeString(releaseType));
|
||||||
releaseContainer->bindString("release-type", releaseHelpers::buildReleaseTypeString(releaseType));
|
|
||||||
else
|
|
||||||
releaseContainer->bindString("release-type", Wt::WString::tr("Lms.Explore.releases")); // fallback when not tagged with MB or custom type
|
|
||||||
|
|
||||||
releases.container = releaseContainer->bindNew<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container"));
|
releases.container = releaseContainer->bindNew<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container"));
|
||||||
releases.container->onRequestElements.connect(this, [this, &releases = releases] {
|
releases.container->onRequestElements.connect(this, [this, &releases = releases] {
|
||||||
addSomeReleases(releases);
|
addSomeReleases(releases);
|
||||||
|
|||||||
@@ -24,10 +24,13 @@
|
|||||||
#include <Wt/WText.h>
|
#include <Wt/WText.h>
|
||||||
|
|
||||||
#include "core/EnumSet.hpp"
|
#include "core/EnumSet.hpp"
|
||||||
|
#include "core/Utils.hpp"
|
||||||
|
|
||||||
#include "database/objects/Artist.hpp"
|
#include "database/objects/Artist.hpp"
|
||||||
#include "database/objects/Release.hpp"
|
#include "database/objects/Release.hpp"
|
||||||
|
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
#include "explore/ReleaseTypes.hpp"
|
||||||
|
|
||||||
namespace lms::ui::releaseListHelpers
|
namespace lms::ui::releaseListHelpers
|
||||||
{
|
{
|
||||||
@@ -110,85 +113,106 @@ namespace lms::ui::releaseListHelpers
|
|||||||
|
|
||||||
namespace lms::ui::releaseHelpers
|
namespace lms::ui::releaseHelpers
|
||||||
{
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
static const Wt::WString releaseTypeSeparator{ " · " };
|
||||||
|
|
||||||
|
Wt::WString buildPicardReleaseTypeString(const PicardReleaseType& releaseType)
|
||||||
|
{
|
||||||
|
Wt::WString res;
|
||||||
|
|
||||||
|
switch (releaseType.primaryType)
|
||||||
|
{
|
||||||
|
case PicardReleaseType::PrimaryType::Album:
|
||||||
|
res = Wt::WString::tr("Lms.Explore.Release.type-primary-album");
|
||||||
|
break;
|
||||||
|
case PicardReleaseType::PrimaryType::Broadcast:
|
||||||
|
res = Wt::WString::tr("Lms.Explore.Release.type-primary-broadcast");
|
||||||
|
break;
|
||||||
|
case PicardReleaseType::PrimaryType::EP:
|
||||||
|
res = Wt::WString::tr("Lms.Explore.Release.type-primary-ep");
|
||||||
|
break;
|
||||||
|
case PicardReleaseType::PrimaryType::Single:
|
||||||
|
res = Wt::WString::tr("Lms.Explore.Release.type-primary-single");
|
||||||
|
break;
|
||||||
|
case PicardReleaseType::PrimaryType::Other:
|
||||||
|
res = Wt::WString::tr("Lms.Explore.Release.type-primary-other");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PicardReleaseType::SecondaryType secondaryType : releaseType.secondaryTypes)
|
||||||
|
{
|
||||||
|
if (!res.empty())
|
||||||
|
res += releaseTypeSeparator;
|
||||||
|
|
||||||
|
switch (secondaryType)
|
||||||
|
{
|
||||||
|
case PicardReleaseType::SecondaryType::Compilation:
|
||||||
|
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-compilation");
|
||||||
|
break;
|
||||||
|
case PicardReleaseType::SecondaryType::Spokenword:
|
||||||
|
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-spokenword");
|
||||||
|
break;
|
||||||
|
case PicardReleaseType::SecondaryType::Soundtrack:
|
||||||
|
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-soundtrack");
|
||||||
|
break;
|
||||||
|
case PicardReleaseType::SecondaryType::Interview:
|
||||||
|
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-interview");
|
||||||
|
break;
|
||||||
|
case PicardReleaseType::SecondaryType::Audiobook:
|
||||||
|
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiobook");
|
||||||
|
break;
|
||||||
|
case PicardReleaseType::SecondaryType::AudioDrama:
|
||||||
|
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiodrama");
|
||||||
|
break;
|
||||||
|
case PicardReleaseType::SecondaryType::Live:
|
||||||
|
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-live");
|
||||||
|
break;
|
||||||
|
case PicardReleaseType::SecondaryType::Remix:
|
||||||
|
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-remix");
|
||||||
|
break;
|
||||||
|
case PicardReleaseType::SecondaryType::DJMix:
|
||||||
|
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-djmix");
|
||||||
|
break;
|
||||||
|
case PicardReleaseType::SecondaryType::Mixtape_Street:
|
||||||
|
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-mixtape-street");
|
||||||
|
break;
|
||||||
|
case PicardReleaseType::SecondaryType::Demo:
|
||||||
|
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-demo");
|
||||||
|
break;
|
||||||
|
case PicardReleaseType::SecondaryType::FieldRecording:
|
||||||
|
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-field-recording");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Wt::WString buildCustomReleaseTypeString(const CustomReleaseType& releaseType)
|
||||||
|
{
|
||||||
|
Wt::WString res;
|
||||||
|
|
||||||
|
for (const std::string& customType : releaseType.types)
|
||||||
|
{
|
||||||
|
if (!res.empty())
|
||||||
|
res += releaseTypeSeparator;
|
||||||
|
|
||||||
|
res += customType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Wt::WString buildReleaseTypeString(const ReleaseType& releaseType)
|
Wt::WString buildReleaseTypeString(const ReleaseType& releaseType)
|
||||||
{
|
{
|
||||||
Wt::WString res;
|
Wt::WString res;
|
||||||
|
|
||||||
if (releaseType.primaryType)
|
std::visit(core::utils::overloads{
|
||||||
{
|
[&](const PicardReleaseType& picardReleaseType) { res = buildPicardReleaseTypeString(picardReleaseType); },
|
||||||
switch (*releaseType.primaryType)
|
[&](const CustomReleaseType& customReleaseType) { res = buildCustomReleaseTypeString(customReleaseType); } },
|
||||||
{
|
releaseType);
|
||||||
case PrimaryReleaseType::Album:
|
|
||||||
res = Wt::WString::tr("Lms.Explore.Release.type-primary-album");
|
|
||||||
break;
|
|
||||||
case PrimaryReleaseType::Broadcast:
|
|
||||||
res = Wt::WString::tr("Lms.Explore.Release.type-primary-broadcast");
|
|
||||||
break;
|
|
||||||
case PrimaryReleaseType::EP:
|
|
||||||
res = Wt::WString::tr("Lms.Explore.Release.type-primary-ep");
|
|
||||||
break;
|
|
||||||
case PrimaryReleaseType::Single:
|
|
||||||
res = Wt::WString::tr("Lms.Explore.Release.type-primary-single");
|
|
||||||
break;
|
|
||||||
case PrimaryReleaseType::Other:
|
|
||||||
res = Wt::WString::tr("Lms.Explore.Release.type-primary-other");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (SecondaryReleaseType secondaryType : releaseType.secondaryTypes)
|
|
||||||
{
|
|
||||||
if (!res.empty())
|
|
||||||
res += Wt::WString{ " · " };
|
|
||||||
|
|
||||||
switch (secondaryType)
|
|
||||||
{
|
|
||||||
case SecondaryReleaseType::Compilation:
|
|
||||||
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-compilation");
|
|
||||||
break;
|
|
||||||
case SecondaryReleaseType::Spokenword:
|
|
||||||
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-spokenword");
|
|
||||||
break;
|
|
||||||
case SecondaryReleaseType::Soundtrack:
|
|
||||||
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-soundtrack");
|
|
||||||
break;
|
|
||||||
case SecondaryReleaseType::Interview:
|
|
||||||
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-interview");
|
|
||||||
break;
|
|
||||||
case SecondaryReleaseType::Audiobook:
|
|
||||||
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiobook");
|
|
||||||
break;
|
|
||||||
case SecondaryReleaseType::AudioDrama:
|
|
||||||
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiodrama");
|
|
||||||
break;
|
|
||||||
case SecondaryReleaseType::Live:
|
|
||||||
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-live");
|
|
||||||
break;
|
|
||||||
case SecondaryReleaseType::Remix:
|
|
||||||
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-remix");
|
|
||||||
break;
|
|
||||||
case SecondaryReleaseType::DJMix:
|
|
||||||
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-djmix");
|
|
||||||
break;
|
|
||||||
case SecondaryReleaseType::Mixtape_Street:
|
|
||||||
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-mixtape-street");
|
|
||||||
break;
|
|
||||||
case SecondaryReleaseType::Demo:
|
|
||||||
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-demo");
|
|
||||||
break;
|
|
||||||
case SecondaryReleaseType::FieldRecording:
|
|
||||||
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-field-recording");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const std::string& customType : releaseType.customTypes)
|
|
||||||
{
|
|
||||||
if (!res.empty())
|
|
||||||
res += Wt::WString{ " · " };
|
|
||||||
|
|
||||||
res += customType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,24 +17,24 @@
|
|||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "ReleaseTypes.hpp"
|
||||||
|
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "core/String.hpp"
|
#include "core/String.hpp"
|
||||||
|
|
||||||
#include "ReleaseTypes.hpp"
|
|
||||||
|
|
||||||
namespace lms::core::stringUtils
|
namespace lms::core::stringUtils
|
||||||
{
|
{
|
||||||
template<>
|
template<>
|
||||||
std::optional<ui::PrimaryReleaseType> readAs(std::string_view str)
|
std::optional<ui::PicardReleaseType::PrimaryType> readAs(std::string_view str)
|
||||||
{
|
{
|
||||||
static const std::unordered_map<std::string, ui::PrimaryReleaseType> entries{
|
static const std::unordered_map<std::string, ui::PicardReleaseType::PrimaryType> entries{
|
||||||
{ "album", ui::PrimaryReleaseType::Album },
|
{ "album", ui::PicardReleaseType::PrimaryType::Album },
|
||||||
{ "single", ui::PrimaryReleaseType::Single },
|
{ "single", ui::PicardReleaseType::PrimaryType::Single },
|
||||||
{ "ep", ui::PrimaryReleaseType::EP },
|
{ "ep", ui::PicardReleaseType::PrimaryType::EP },
|
||||||
{ "broadcast", ui::PrimaryReleaseType::Broadcast },
|
{ "broadcast", ui::PicardReleaseType::PrimaryType::Broadcast },
|
||||||
{ "other", ui::PrimaryReleaseType::Other },
|
{ "other", ui::PicardReleaseType::PrimaryType::Other },
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto it{ entries.find(stringToLower(stringTrim(str))) };
|
const auto it{ entries.find(stringToLower(stringTrim(str))) };
|
||||||
@@ -45,21 +45,21 @@ namespace lms::core::stringUtils
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
std::optional<ui::SecondaryReleaseType> readAs(std::string_view str)
|
std::optional<ui::PicardReleaseType::SecondaryType> readAs(std::string_view str)
|
||||||
{
|
{
|
||||||
static const std::unordered_map<std::string, ui::SecondaryReleaseType> entries{
|
static const std::unordered_map<std::string, ui::PicardReleaseType::SecondaryType> entries{
|
||||||
{ "compilation", ui::SecondaryReleaseType::Compilation },
|
{ "compilation", ui::PicardReleaseType::SecondaryType::Compilation },
|
||||||
{ "soundtrack", ui::SecondaryReleaseType::Soundtrack },
|
{ "soundtrack", ui::PicardReleaseType::SecondaryType::Soundtrack },
|
||||||
{ "spokenword", ui::SecondaryReleaseType::Spokenword },
|
{ "spokenword", ui::PicardReleaseType::SecondaryType::Spokenword },
|
||||||
{ "interview", ui::SecondaryReleaseType::Interview },
|
{ "interview", ui::PicardReleaseType::SecondaryType::Interview },
|
||||||
{ "audiobook", ui::SecondaryReleaseType::Audiobook },
|
{ "audiobook", ui::PicardReleaseType::SecondaryType::Audiobook },
|
||||||
{ "audio drama", ui::SecondaryReleaseType::AudioDrama },
|
{ "audio drama", ui::PicardReleaseType::SecondaryType::AudioDrama },
|
||||||
{ "live", ui::SecondaryReleaseType::Live },
|
{ "live", ui::PicardReleaseType::SecondaryType::Live },
|
||||||
{ "remix", ui::SecondaryReleaseType::Remix },
|
{ "remix", ui::PicardReleaseType::SecondaryType::Remix },
|
||||||
{ "dj-mix", ui::SecondaryReleaseType::DJMix },
|
{ "dj-mix", ui::PicardReleaseType::SecondaryType::DJMix },
|
||||||
{ "mixtape/street", ui::SecondaryReleaseType::Mixtape_Street },
|
{ "mixtape/street", ui::PicardReleaseType::SecondaryType::Mixtape_Street },
|
||||||
{ "demo", ui::SecondaryReleaseType::Demo },
|
{ "demo", ui::PicardReleaseType::SecondaryType::Demo },
|
||||||
{ "field recording", ui::SecondaryReleaseType::FieldRecording },
|
{ "field recording", ui::PicardReleaseType::SecondaryType::FieldRecording },
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto it{ entries.find(stringToLower(stringTrim(str))) };
|
const auto it{ entries.find(stringToLower(stringTrim(str))) };
|
||||||
@@ -72,48 +72,48 @@ namespace lms::core::stringUtils
|
|||||||
|
|
||||||
namespace lms::ui
|
namespace lms::ui
|
||||||
{
|
{
|
||||||
ReleaseType parseReleaseType(const std::vector<std::string>& releaseTypeNames)
|
std::optional<PicardReleaseType> parsePicardReleaseType(const std::vector<std::string>& releaseTypeNames)
|
||||||
{
|
{
|
||||||
ReleaseType res;
|
if (releaseTypeNames.empty())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
for (std::string_view releaseTypeName : releaseTypeNames)
|
const auto primaryType{ core::stringUtils::readAs<PicardReleaseType::PrimaryType>(releaseTypeNames[0]) };
|
||||||
|
if (!primaryType)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
PicardReleaseType res{ .primaryType = *primaryType, .secondaryTypes = {} };
|
||||||
|
for (std::size_t i{ 1 }; i < releaseTypeNames.size(); ++i)
|
||||||
{
|
{
|
||||||
if (auto primaryType{ core::stringUtils::readAs<PrimaryReleaseType>(releaseTypeName) })
|
const auto secondaryType{ core::stringUtils::readAs<PicardReleaseType::SecondaryType>(releaseTypeNames[i]) };
|
||||||
{
|
if (!secondaryType)
|
||||||
if (!res.primaryType)
|
return std::nullopt;
|
||||||
res.primaryType = primaryType;
|
|
||||||
else
|
res.secondaryTypes.insert(*secondaryType);
|
||||||
res.customTypes.push_back(std::string{ releaseTypeName });
|
|
||||||
}
|
|
||||||
else if (auto secondaryType{ core::stringUtils::readAs<SecondaryReleaseType>(releaseTypeName) })
|
|
||||||
{
|
|
||||||
res.secondaryTypes.insert(*secondaryType);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
res.customTypes.push_back(std::string{ releaseTypeName });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator<(std::optional<PrimaryReleaseType> typeA, std::optional<PrimaryReleaseType> typeB)
|
ReleaseType parseReleaseType(const std::vector<std::string>& releaseTypeNames)
|
||||||
{
|
{
|
||||||
if (!typeA && typeB)
|
if (const auto picardReleaseType{ parsePicardReleaseType(releaseTypeNames) })
|
||||||
return false;
|
return *picardReleaseType;
|
||||||
else if (typeA && !typeB)
|
|
||||||
return true;
|
return CustomReleaseType{ .types = releaseTypeNames };
|
||||||
else
|
|
||||||
return static_cast<int>(*typeA) < static_cast<int>(*typeB);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator<(core::EnumSet<SecondaryReleaseType> typesA, core::EnumSet<SecondaryReleaseType> typesB)
|
bool operator<(core::EnumSet<PicardReleaseType::SecondaryType> lhs, core::EnumSet<PicardReleaseType::SecondaryType> rhs)
|
||||||
{
|
{
|
||||||
return typesA.getBitfield() < typesB.getBitfield();
|
return lhs.getBitfield() < rhs.getBitfield();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReleaseType::operator<(const ReleaseType& other) const
|
bool PicardReleaseType::operator<(const PicardReleaseType& other) const
|
||||||
{
|
{
|
||||||
// TODO : order custom types and compare for each element (size is not to be compared first)
|
return std::tie(primaryType, secondaryTypes) < std::tie(other.primaryType, other.secondaryTypes);
|
||||||
return std::tie(primaryType, secondaryTypes, customTypes) < std::tie(other.primaryType, other.secondaryTypes, other.customTypes);
|
}
|
||||||
|
|
||||||
|
bool CustomReleaseType::operator<(const CustomReleaseType& other) const
|
||||||
|
{
|
||||||
|
return types < other.types;
|
||||||
}
|
}
|
||||||
} // namespace lms::ui
|
} // namespace lms::ui
|
||||||
|
|||||||
@@ -19,49 +19,55 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "core/EnumSet.hpp"
|
#include "core/EnumSet.hpp"
|
||||||
|
|
||||||
namespace lms::ui
|
namespace lms::ui
|
||||||
{
|
{
|
||||||
// see https://musicbrainz.org/doc/Release_Group/Type
|
struct PicardReleaseType
|
||||||
enum class PrimaryReleaseType
|
|
||||||
{
|
{
|
||||||
Album,
|
// see https://musicbrainz.org/doc/Release_Group/Type
|
||||||
Single,
|
enum class PrimaryType : std::uint8_t
|
||||||
EP,
|
{
|
||||||
Broadcast,
|
Album,
|
||||||
Other
|
Single,
|
||||||
|
EP,
|
||||||
|
Broadcast,
|
||||||
|
Other
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SecondaryType : std::uint8_t
|
||||||
|
{
|
||||||
|
Compilation,
|
||||||
|
Soundtrack,
|
||||||
|
Spokenword,
|
||||||
|
Interview,
|
||||||
|
Audiobook,
|
||||||
|
AudioDrama,
|
||||||
|
Live,
|
||||||
|
Remix,
|
||||||
|
DJMix,
|
||||||
|
Mixtape_Street,
|
||||||
|
Demo,
|
||||||
|
FieldRecording,
|
||||||
|
};
|
||||||
|
|
||||||
|
PrimaryType primaryType;
|
||||||
|
core::EnumSet<SecondaryType> secondaryTypes;
|
||||||
|
|
||||||
|
bool operator<(const PicardReleaseType&) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class SecondaryReleaseType
|
struct CustomReleaseType
|
||||||
{
|
{
|
||||||
Compilation,
|
std::vector<std::string> types;
|
||||||
Soundtrack,
|
|
||||||
Spokenword,
|
bool operator<(const CustomReleaseType&) const;
|
||||||
Interview,
|
|
||||||
Audiobook,
|
|
||||||
AudioDrama,
|
|
||||||
Live,
|
|
||||||
Remix,
|
|
||||||
DJMix,
|
|
||||||
Mixtape_Street,
|
|
||||||
Demo,
|
|
||||||
FieldRecording,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ReleaseType
|
|
||||||
{
|
|
||||||
std::optional<PrimaryReleaseType> primaryType;
|
|
||||||
core::EnumSet<SecondaryReleaseType> secondaryTypes;
|
|
||||||
std::vector<std::string> customTypes;
|
|
||||||
|
|
||||||
bool operator<(const ReleaseType& other) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using ReleaseType = std::variant<PicardReleaseType, CustomReleaseType>;
|
||||||
ReleaseType parseReleaseType(const std::vector<std::string>& releaseTypeNames);
|
ReleaseType parseReleaseType(const std::vector<std::string>& releaseTypeNames);
|
||||||
|
|
||||||
} // namespace lms::ui
|
} // namespace lms::ui
|
||||||
Reference in New Issue
Block a user