mirror of https://github.com/AxioDL/metaforce.git
Some editor stubs
This commit is contained in:
parent
7a864e6dd2
commit
e8e40211d9
|
@ -1,9 +1,6 @@
|
|||
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_subdirectory(locale)
|
||||
|
||||
atdna(atdna_Space.cpp Space.hpp)
|
||||
atdna(atdna_ResourceOutliner.cpp ResourceOutliner.hpp)
|
||||
|
||||
if("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
|
||||
option(RUDE_BINARY_CONFIGS
|
||||
"Use binary Athena formats for serializing save data structures, rather than YAML"
|
||||
|
@ -18,11 +15,15 @@ if(RUDE_BINARY_CONFIGS)
|
|||
add_definitions("-DURDE_BINARY_CONFIGS=1")
|
||||
endif()
|
||||
|
||||
atdna(atdna_Space.cpp Space.hpp)
|
||||
atdna(atdna_ResourceBrowser.cpp ResourceBrowser.hpp)
|
||||
|
||||
add_executable(urde WIN32
|
||||
main.cpp
|
||||
Space.hpp Space.cpp atdna_Space.cpp
|
||||
SplashScreen.hpp SplashScreen.cpp
|
||||
ResourceOutliner.hpp ResourceOutliner.cpp atdna_ResourceOutliner.cpp
|
||||
ResourceBrowser.hpp ResourceBrowser.cpp atdna_ResourceBrowser.cpp
|
||||
ModelViewer.hpp ModelViewer.cpp
|
||||
ProjectManager.hpp ProjectManager.cpp
|
||||
ViewManager.hpp ViewManager.cpp)
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef URDE_MODEL_VIEWER_HPP
|
||||
#define URDE_MODEL_VIEWER_HPP
|
||||
|
||||
namespace URDE
|
||||
{
|
||||
}
|
||||
|
||||
#endif // URDE_MODEL_VIEWER_HPP
|
|
@ -121,7 +121,7 @@ bool ProjectManager::saveProject()
|
|||
if (!m_proj)
|
||||
return false;
|
||||
|
||||
#ifdef urde_BINARY_CONFIGS
|
||||
#ifdef URDE_BINARY_CONFIGS
|
||||
HECL::ProjectPath oldSpacesPath(*m_proj, _S(".hecl/~urde_spaces.bin"));
|
||||
Athena::io::FileWriter w(oldSpacesPath.getAbsolutePath(), true, false);
|
||||
if (w.hasError())
|
||||
|
|
|
@ -25,6 +25,8 @@ public:
|
|||
ProjectManager(ViewManager& vm);
|
||||
operator bool() const {return m_proj.operator bool();}
|
||||
|
||||
HECL::Database::Project* project() {return m_proj.get();}
|
||||
|
||||
bool newProject(const HECL::SystemString& path);
|
||||
bool openProject(const HECL::SystemString& path);
|
||||
bool extractGame(const HECL::SystemString& path);
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
#include "ResourceBrowser.hpp"
|
||||
|
||||
namespace URDE
|
||||
{
|
||||
#define BROWSER_MARGIN 8
|
||||
|
||||
bool ResourceBrowser::navigateToPath(const HECL::ProjectPath& pathIn)
|
||||
{
|
||||
if (pathIn.getPathType() == HECL::ProjectPath::Type::File)
|
||||
m_path = pathIn.getParentPath();
|
||||
else
|
||||
m_path = pathIn;
|
||||
|
||||
m_comps = m_path.getPathComponents();
|
||||
|
||||
HECL::DirectoryEnumerator dEnum(m_path.getAbsolutePath(), HECL::DirectoryEnumerator::Mode::DirsThenFilesSorted,
|
||||
m_state.sortColumn==State::SortColumn::Size,
|
||||
m_state.sortDir==Specter::SortDirection::Descending,
|
||||
true);
|
||||
m_fileListingBind.updateListing(dEnum);
|
||||
m_view->m_fileListing.m_view->selectRow(-1);
|
||||
m_view->m_fileListing.m_view->updateData();
|
||||
m_view->m_pathButtons.m_view->setButtons(m_comps);
|
||||
|
||||
m_view->updateSize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ResourceBrowser::pathButtonActivated(size_t idx)
|
||||
{
|
||||
if (idx >= m_comps.size())
|
||||
return;
|
||||
|
||||
HECL::SystemString dir;
|
||||
bool needSlash = false;
|
||||
size_t i = 0;
|
||||
for (const HECL::SystemString& d : m_comps)
|
||||
{
|
||||
if (needSlash)
|
||||
dir += _S('/');
|
||||
if (d.compare(_S("/")))
|
||||
needSlash = true;
|
||||
dir += d;
|
||||
if (++i > idx)
|
||||
break;
|
||||
}
|
||||
navigateToPath(HECL::ProjectPath(*m_vm.project(), dir));
|
||||
}
|
||||
|
||||
void ResourceBrowser::View::resized(const boo::SWindowRect& root, const boo::SWindowRect& sub)
|
||||
{
|
||||
float pf = rootView().viewRes().pixelFactor();
|
||||
|
||||
boo::SWindowRect pathRect = sub;
|
||||
pathRect.location[0] += BROWSER_MARGIN * pf;
|
||||
pathRect.location[1] += pathRect.size[1] - (BROWSER_MARGIN + 20) * pf;
|
||||
m_pathButtons.m_view->resized(root, pathRect);
|
||||
}
|
||||
void ResourceBrowser::View::draw(boo::IGraphicsCommandQueue* gfxQ)
|
||||
{
|
||||
m_pathButtons.m_view->draw(gfxQ);
|
||||
m_fileListing.m_view->draw(gfxQ);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
#ifndef URDE_RESOURCE_OUTLINER_HPP
|
||||
#define URDE_RESOURCE_OUTLINER_HPP
|
||||
|
||||
#include "Space.hpp"
|
||||
#include "ViewManager.hpp"
|
||||
#include "Specter/PathButtons.hpp"
|
||||
|
||||
namespace URDE
|
||||
{
|
||||
|
||||
class ResourceBrowser : public Space, public Specter::IPathButtonsBinding
|
||||
{
|
||||
struct State : Space::State
|
||||
{
|
||||
DECL_YAML
|
||||
String<-1> path;
|
||||
Value<float> columnSplits[3] = {0.0f, 0.7f, 0.9f};
|
||||
enum class SortColumn
|
||||
{
|
||||
Name,
|
||||
Type,
|
||||
Size
|
||||
};
|
||||
Value<SortColumn> sortColumn = SortColumn::Name;
|
||||
Value<Specter::SortDirection> sortDir = Specter::SortDirection::Ascending;
|
||||
} m_state;
|
||||
const Space::State& spaceState() const {return m_state;}
|
||||
|
||||
HECL::ProjectPath m_path;
|
||||
std::vector<HECL::SystemString> m_comps;
|
||||
|
||||
void pathButtonActivated(size_t idx);
|
||||
|
||||
struct ResListingDataBind : Specter::ITableDataBinding, Specter::ITableStateBinding
|
||||
{
|
||||
ResourceBrowser& m_rb;
|
||||
|
||||
struct Entry
|
||||
{
|
||||
HECL::SystemString m_path;
|
||||
std::string m_name;
|
||||
std::string m_type;
|
||||
std::string m_size;
|
||||
};
|
||||
std::vector<Entry> m_entries;
|
||||
|
||||
std::string m_nameCol;
|
||||
std::string m_typeCol;
|
||||
std::string m_sizeCol;
|
||||
|
||||
std::string m_dirStr;
|
||||
std::string m_projStr;
|
||||
std::string m_fileStr;
|
||||
|
||||
size_t columnCount() const {return 3;}
|
||||
size_t rowCount() const {return m_entries.size();}
|
||||
|
||||
const std::string* header(size_t cIdx) const
|
||||
{
|
||||
switch (cIdx)
|
||||
{
|
||||
case 0:
|
||||
return &m_nameCol;
|
||||
case 1:
|
||||
return &m_typeCol;
|
||||
case 2:
|
||||
return &m_sizeCol;
|
||||
default: break;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::string* cell(size_t cIdx, size_t rIdx) const
|
||||
{
|
||||
switch (cIdx)
|
||||
{
|
||||
case 0:
|
||||
return &m_entries.at(rIdx).m_name;
|
||||
case 1:
|
||||
return &m_entries.at(rIdx).m_type;
|
||||
case 2:
|
||||
return &m_entries.at(rIdx).m_size;
|
||||
default: break;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool columnSplitResizeAllowed() const {return true;}
|
||||
|
||||
float getColumnSplit(size_t cIdx) const
|
||||
{
|
||||
return m_rb.m_state.columnSplits[cIdx];
|
||||
}
|
||||
|
||||
void setColumnSplit(size_t cIdx, float split)
|
||||
{
|
||||
m_rb.m_state.columnSplits[cIdx] = split;
|
||||
}
|
||||
|
||||
void updateListing(const HECL::DirectoryEnumerator& dEnum)
|
||||
{
|
||||
m_entries.clear();
|
||||
m_entries.reserve(dEnum.size());
|
||||
|
||||
for (const HECL::DirectoryEnumerator::Entry& d : dEnum)
|
||||
{
|
||||
m_entries.emplace_back();
|
||||
Entry& ent = m_entries.back();
|
||||
ent.m_path = d.m_path;
|
||||
HECL::SystemUTF8View nameUtf8(d.m_name);
|
||||
ent.m_name = nameUtf8.str();
|
||||
if (d.m_isDir)
|
||||
{
|
||||
if (HECL::SearchForProject(d.m_path))
|
||||
ent.m_type = m_projStr;
|
||||
else
|
||||
ent.m_type = m_dirStr;
|
||||
}
|
||||
else
|
||||
{
|
||||
ent.m_type = m_fileStr;
|
||||
ent.m_size = HECL::HumanizeNumber(d.m_fileSz, 7, nullptr, int(HECL::HNScale::AutoScale),
|
||||
HECL::HNFlags::B | HECL::HNFlags::Decimal);
|
||||
}
|
||||
}
|
||||
|
||||
m_needsUpdate = false;
|
||||
}
|
||||
|
||||
bool m_needsUpdate = false;
|
||||
|
||||
Specter::SortDirection getSort(size_t& cIdx) const
|
||||
{
|
||||
cIdx = size_t(m_rb.m_state.sortColumn);
|
||||
if (cIdx > 2)
|
||||
cIdx = 0;
|
||||
return m_rb.m_state.sortDir;
|
||||
}
|
||||
|
||||
void setSort(size_t cIdx, Specter::SortDirection dir)
|
||||
{
|
||||
m_rb.m_state.sortDir = dir;
|
||||
m_needsUpdate = true;
|
||||
}
|
||||
|
||||
void setSelectedRow(size_t rIdx)
|
||||
{
|
||||
}
|
||||
|
||||
void rowActivated(size_t rIdx)
|
||||
{
|
||||
}
|
||||
|
||||
ResListingDataBind(ResourceBrowser& rb, const Specter::IViewManager& vm)
|
||||
: m_rb(rb)
|
||||
{
|
||||
m_nameCol = vm.translateOr("name", "Name");
|
||||
m_typeCol = vm.translateOr("type", "Type");
|
||||
m_sizeCol = vm.translateOr("size", "Size");
|
||||
m_dirStr = vm.translateOr("directory", "Directory");
|
||||
m_projStr = vm.translateOr("hecl_project", "HECL Project");
|
||||
m_fileStr = vm.translateOr("file", "File");
|
||||
}
|
||||
|
||||
} m_fileListingBind;
|
||||
|
||||
struct View : Specter::View
|
||||
{
|
||||
ResourceBrowser& m_ro;
|
||||
Specter::ViewChild<std::unique_ptr<Specter::PathButtons>> m_pathButtons;
|
||||
Specter::ViewChild<std::unique_ptr<Specter::Table>> m_fileListing;
|
||||
|
||||
View(ResourceBrowser& ro, Specter::ViewResources& res)
|
||||
: Specter::View(res, ro.m_vm.rootView()), m_ro(ro)
|
||||
{
|
||||
commitResources(res);
|
||||
m_pathButtons.m_view.reset(new Specter::PathButtons(res, *this, ro));
|
||||
m_fileListing.m_view.reset(new Specter::Table(res, *this, &ro.m_fileListingBind, &ro.m_fileListingBind, 3));
|
||||
}
|
||||
|
||||
void resized(const boo::SWindowRect& root, const boo::SWindowRect& sub);
|
||||
void draw(boo::IGraphicsCommandQueue* gfxQ);
|
||||
};
|
||||
std::unique_ptr<View> m_view;
|
||||
|
||||
public:
|
||||
ResourceBrowser(ViewManager& vm)
|
||||
: Space(vm, Class::ResourceBrowser),
|
||||
m_fileListingBind(*this, vm)
|
||||
{
|
||||
m_state.path = vm.project()->getProjectWorkingPath().getRelativePathUTF8();
|
||||
reloadState();
|
||||
}
|
||||
ResourceBrowser(ViewManager& vm, ConfigReader& r)
|
||||
: ResourceBrowser(vm)
|
||||
{
|
||||
m_state.read(r);
|
||||
reloadState();
|
||||
}
|
||||
|
||||
void reloadState()
|
||||
{
|
||||
navigateToPath(HECL::ProjectPath(*m_vm.project(), m_state.path));
|
||||
}
|
||||
|
||||
bool navigateToPath(const HECL::ProjectPath& path);
|
||||
|
||||
Specter::View* buildContentView(Specter::ViewResources& res)
|
||||
{
|
||||
m_view.reset(new View(*this, res));
|
||||
return m_view.get();
|
||||
}
|
||||
|
||||
bool usesToolbar() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // URDE_RESOURCE_OUTLINER_HPP
|
|
@ -1,6 +0,0 @@
|
|||
#include "ResourceOutliner.hpp"
|
||||
|
||||
namespace URDE
|
||||
{
|
||||
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
#ifndef URDE_RESOURCE_OUTLINER_HPP
|
||||
#define URDE_RESOURCE_OUTLINER_HPP
|
||||
|
||||
#include "Space.hpp"
|
||||
#include "ViewManager.hpp"
|
||||
|
||||
namespace URDE
|
||||
{
|
||||
|
||||
class ResourceOutliner : public Space
|
||||
{
|
||||
struct State : Space::State
|
||||
{
|
||||
DECL_YAML
|
||||
} m_state;
|
||||
const Space::State& spaceState() const {return m_state;}
|
||||
|
||||
struct View : public Specter::View
|
||||
{
|
||||
ResourceOutliner& m_ro;
|
||||
View(ResourceOutliner& ro, Specter::ViewResources& res)
|
||||
: Specter::View(res, ro.m_vm.rootView()), m_ro(ro)
|
||||
{
|
||||
commitResources(res);
|
||||
setBackground(Zeus::CColor::skBlue);
|
||||
}
|
||||
};
|
||||
std::unique_ptr<View> m_view;
|
||||
|
||||
public:
|
||||
ResourceOutliner(ViewManager& vm) : Space(vm, Class::ResourceOutliner) {}
|
||||
ResourceOutliner(ViewManager& vm, ConfigReader& r)
|
||||
: ResourceOutliner(vm)
|
||||
{
|
||||
m_state.read(r);
|
||||
}
|
||||
|
||||
Specter::View* buildContentView(Specter::ViewResources& res)
|
||||
{
|
||||
m_view.reset(new View(*this, res));
|
||||
return m_view.get();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // URDE_RESOURCE_OUTLINER_HPP
|
|
@ -1,6 +1,6 @@
|
|||
#include "Space.hpp"
|
||||
#include "ViewManager.hpp"
|
||||
#include "ResourceOutliner.hpp"
|
||||
#include "ResourceBrowser.hpp"
|
||||
|
||||
namespace URDE
|
||||
{
|
||||
|
@ -8,12 +8,21 @@ static LogVisor::LogModule Log("URDE::Space");
|
|||
|
||||
Specter::View* Space::buildSpaceView(Specter::ViewResources& res)
|
||||
{
|
||||
if (usesToolbar())
|
||||
{
|
||||
m_space.reset(new Specter::Space(res, m_vm.rootView(), Specter::Toolbar::Position::Bottom));
|
||||
Specter::View* sview = buildContentView(res);
|
||||
m_space->setContentView(sview);
|
||||
if (usesToolbar())
|
||||
buildToolbarView(res, m_space->toolbar());
|
||||
buildToolbarView(res, *m_space->toolbar());
|
||||
return m_space.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_space.reset(new Specter::Space(res, m_vm.rootView(), Specter::Toolbar::Position::None));
|
||||
Specter::View* sview = buildContentView(res);
|
||||
m_space->setContentView(sview);
|
||||
return m_space.get();
|
||||
}
|
||||
}
|
||||
|
||||
Specter::View* SplitSpace::buildContentView(Specter::ViewResources& res)
|
||||
|
@ -41,13 +50,25 @@ static Space* BuildNewSpace(ViewManager& vm, Space::Class cls, Reader& r)
|
|||
{
|
||||
case Class::SplitSpace:
|
||||
return new SplitSpace(vm, r);
|
||||
case Class::ResourceOutliner:
|
||||
return new ResourceOutliner(vm, r);
|
||||
case Class::ResourceBrowser:
|
||||
return new ResourceBrowser(vm, r);
|
||||
default: break;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Space::saveState(Athena::io::IStreamWriter& w) const
|
||||
{
|
||||
w.writeUint32Big(atUint32(m_class));
|
||||
spaceState().write(w);
|
||||
}
|
||||
|
||||
void Space::saveState(Athena::io::YAMLDocWriter& w) const
|
||||
{
|
||||
w.writeUint32("class", atUint32(m_class));
|
||||
spaceState().write(w);
|
||||
}
|
||||
|
||||
Space* Space::NewSpaceFromConfigStream(ViewManager& vm, ConfigReader& r)
|
||||
{
|
||||
#ifdef URDE_BINARY_CONFIGS
|
||||
|
|
|
@ -28,7 +28,7 @@ public:
|
|||
None,
|
||||
SplitSpace,
|
||||
TestSpace,
|
||||
ResourceOutliner,
|
||||
ResourceBrowser,
|
||||
};
|
||||
|
||||
struct State : Athena::io::DNAYaml<Athena::BigEndian> {Delete _d;};
|
||||
|
@ -51,17 +51,9 @@ protected:
|
|||
virtual Specter::View* buildSpaceView(Specter::ViewResources& res);
|
||||
|
||||
public:
|
||||
virtual void saveState(Athena::io::IStreamWriter& w) const
|
||||
{
|
||||
w.writeUint32Big(atUint32(m_class));
|
||||
spaceState().write(w);
|
||||
}
|
||||
|
||||
virtual void saveState(Athena::io::YAMLDocWriter& w) const
|
||||
{
|
||||
w.writeUint32("class", atUint32(m_class));
|
||||
spaceState().write(w);
|
||||
}
|
||||
virtual void saveState(Athena::io::IStreamWriter& w) const;
|
||||
virtual void saveState(Athena::io::YAMLDocWriter& w) const;
|
||||
virtual void reloadState() {}
|
||||
|
||||
virtual void think() {}
|
||||
};
|
||||
|
|
|
@ -35,9 +35,9 @@ class SplashScreen : public Specter::ModalWindow
|
|||
{
|
||||
SplashScreen& m_splash;
|
||||
NewProjBinding(SplashScreen& splash) : m_splash(splash) {}
|
||||
const char* name() const {return m_splash.m_newString.c_str();}
|
||||
const char* help() const {return "Creates an empty project at selected path";}
|
||||
void activated(const boo::SWindowCoord& coord)
|
||||
const char* name(const Specter::Control* control) const {return m_splash.m_newString.c_str();}
|
||||
const char* help(const Specter::Control* control) const {return "Creates an empty project at selected path";}
|
||||
void activated(const Specter::Button* button, const boo::SWindowCoord& coord)
|
||||
{
|
||||
m_splash.m_fileBrowser.m_view.reset(
|
||||
new Specter::FileBrowser(m_splash.rootView().viewRes(),
|
||||
|
@ -60,9 +60,9 @@ class SplashScreen : public Specter::ModalWindow
|
|||
{
|
||||
SplashScreen& m_splash;
|
||||
OpenProjBinding(SplashScreen& splash) : m_splash(splash) {}
|
||||
const char* name() const {return m_splash.m_openString.c_str();}
|
||||
const char* help() const {return "Opens an existing project at selected path";}
|
||||
void activated(const boo::SWindowCoord& coord)
|
||||
const char* name(const Specter::Control* control) const {return m_splash.m_openString.c_str();}
|
||||
const char* help(const Specter::Control* control) const {return "Opens an existing project at selected path";}
|
||||
void activated(const Specter::Button* button, const boo::SWindowCoord& coord)
|
||||
{
|
||||
m_splash.m_fileBrowser.m_view.reset(
|
||||
new Specter::FileBrowser(m_splash.rootView().viewRes(),
|
||||
|
@ -85,9 +85,9 @@ class SplashScreen : public Specter::ModalWindow
|
|||
{
|
||||
SplashScreen& m_splash;
|
||||
ExtractProjBinding(SplashScreen& splash) : m_splash(splash) {}
|
||||
const char* name() const {return m_splash.m_extractString.c_str();}
|
||||
const char* help() const {return "Extracts game image as project at selected path";}
|
||||
void activated(const boo::SWindowCoord& coord)
|
||||
const char* name(const Specter::Control* control) const {return m_splash.m_extractString.c_str();}
|
||||
const char* help(const Specter::Control* control) const {return "Extracts game image as project at selected path";}
|
||||
void activated(const Specter::Button* button, const boo::SWindowCoord& coord)
|
||||
{
|
||||
m_splash.m_fileBrowser.m_view.reset(
|
||||
new Specter::FileBrowser(m_splash.rootView().viewRes(),
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "Specter/Space.hpp"
|
||||
#include "SplashScreen.hpp"
|
||||
#include "locale/locale.hpp"
|
||||
#include "ResourceOutliner.hpp"
|
||||
#include "ResourceBrowser.hpp"
|
||||
|
||||
using YAMLNode = Athena::io::YAMLNode;
|
||||
|
||||
|
@ -34,8 +34,8 @@ SplashScreen* ViewManager::SetupSplashView()
|
|||
void ViewManager::SetupEditorView()
|
||||
{
|
||||
SplitSpace* split = new SplitSpace(*this);
|
||||
split->setSpaceSlot(0, std::make_unique<ResourceOutliner>(*this));
|
||||
split->setSpaceSlot(1, std::make_unique<ResourceOutliner>(*this));
|
||||
split->setSpaceSlot(0, std::make_unique<ResourceBrowser>(*this));
|
||||
split->setSpaceSlot(1, std::make_unique<ResourceBrowser>(*this));
|
||||
m_rootSpace.reset(split);
|
||||
|
||||
std::vector<Specter::View*>& cViews = m_rootView->accessContentViews();
|
||||
|
|
|
@ -57,6 +57,7 @@ public:
|
|||
}
|
||||
|
||||
ProjectManager& projectManager() {return m_projManager;}
|
||||
HECL::Database::Project* project() {return m_projManager.project();}
|
||||
const Specter::Translator* getTranslator() const {return &m_translator;}
|
||||
|
||||
const std::vector<HECL::SystemString>* recentProjects() const {return &m_recentProjects;}
|
||||
|
|
2
hecl
2
hecl
|
@ -1 +1 @@
|
|||
Subproject commit cb8fd67a26f7ccba8958c8478029d0695ae0e04b
|
||||
Subproject commit b3f184035a1cf3a152c5614f75964dd597ebc401
|
|
@ -1 +1 @@
|
|||
Subproject commit 323cdc7a845d12497a946d18b6d2f34db88f455c
|
||||
Subproject commit 769e460d2dd2f4db2d2b54ab31436cb3f879488f
|
Loading…
Reference in New Issue