Primfeed integration V1
baseline Primfeed authentication/authorisation - working chat interception login authentication - working Primfeed Integration - WiP Authorisation connect/disconnect - working Primfeed post photo support - working Clean up ready for releasemaster
parent
fb7592f1ca
commit
c085682b54
|
|
@ -96,6 +96,7 @@ set(viewer_SOURCE_FILES
|
|||
dialogstack.cpp
|
||||
exoflickr.cpp
|
||||
exoflickrauth.cpp
|
||||
fsprimfeedauth.cpp
|
||||
exogroupmutelist.cpp
|
||||
floatermedialists.cpp
|
||||
fsareasearch.cpp
|
||||
|
|
@ -164,6 +165,7 @@ set(viewer_SOURCE_FILES
|
|||
fspose.cpp
|
||||
fsposeranimator.cpp
|
||||
fsposingmotion.cpp
|
||||
fsprimfeedauth.cpp
|
||||
fsradar.cpp
|
||||
fsradarentry.cpp
|
||||
fsradarlistctrl.cpp
|
||||
|
|
@ -182,6 +184,8 @@ set(viewer_SOURCE_FILES
|
|||
lfsimfeaturehandler.cpp
|
||||
llflickrconnect.cpp
|
||||
llfloaterflickr.cpp
|
||||
fsprimfeedconnect.cpp
|
||||
fsfloaterprimfeed.cpp
|
||||
llpanelopenregionsettings.cpp
|
||||
# <FS:Ansariel> [Legacy Bake]
|
||||
llagentwearablesfetch.cpp
|
||||
|
|
@ -909,6 +913,7 @@ set(viewer_HEADER_FILES
|
|||
dialogstack.h
|
||||
exoflickr.h
|
||||
exoflickrauth.h
|
||||
fsprimfeedauth.h
|
||||
exogroupmutelist.h
|
||||
floatermedialists.h
|
||||
fsareasearch.h
|
||||
|
|
@ -979,6 +984,7 @@ set(viewer_HEADER_FILES
|
|||
fspose.h
|
||||
fsposeranimator.h
|
||||
fsposingmotion.h
|
||||
fsprimfeedauth.h
|
||||
fsradar.h
|
||||
fsradarentry.h
|
||||
fsradarlistctrl.h
|
||||
|
|
@ -998,6 +1004,8 @@ set(viewer_HEADER_FILES
|
|||
lfsimfeaturehandler.h
|
||||
llflickrconnect.h
|
||||
llfloaterflickr.h
|
||||
fsprimfeedconnect.h
|
||||
fsfloaterprimfeed.h
|
||||
# <FS:Ansariel> [Legacy Bake]
|
||||
llagentwearablesfetch.h
|
||||
vjlocalmesh.h
|
||||
|
|
|
|||
|
|
@ -254,6 +254,16 @@
|
|||
is_running_function="Floater.IsOpen"
|
||||
is_running_parameters="flickr"
|
||||
/>
|
||||
<command name="primfeed"
|
||||
available_in_toybox="true"
|
||||
icon="Command_Primfeed_Icon"
|
||||
label_ref="Command_Primfeed_Label"
|
||||
tooltip_ref="Command_Primfeed_Tooltip"
|
||||
execute_function="Floater.Toggle"
|
||||
execute_parameters="primfeed"
|
||||
is_running_function="Floater.IsOpen"
|
||||
is_running_parameters="primfeed"
|
||||
/>
|
||||
<command name="speak"
|
||||
available_in_toybox="true"
|
||||
icon="Command_Speak_Icon"
|
||||
|
|
|
|||
|
|
@ -153,6 +153,17 @@
|
|||
<key>Value</key>
|
||||
<string>http://phoenixviewer.com/app/fsdata/grids.xml</string>
|
||||
</map>
|
||||
<key>FSPrimfeedViewerApiKey</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Viewer key for API login.</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>String</string>
|
||||
<key>Value</key>
|
||||
<string>xAcXYt8SBius3Lor4wHle8L96PDHYlAZuWYXIYQUdW4b09mjhQUAwiqmWp5UNYXLpq5GSUtuKHuDYLwaueACPkew93l6MRY8jfBKSH09kv0zyGglpky07X7X7Sp4Rzin</string>
|
||||
</map>
|
||||
<key>FSGridBuilderURL</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
|
|
@ -18542,6 +18553,7 @@ Change of this parameter will affect the layout of buttons in notification toast
|
|||
<string>world_map</string>
|
||||
<string>preferences</string>
|
||||
<string>flickr</string>
|
||||
<string>primfeed</string>
|
||||
</array>
|
||||
<key>Backup</key>
|
||||
<integer>0</integer>
|
||||
|
|
@ -24729,6 +24741,39 @@ Change of this parameter will affect the layout of buttons in notification toast
|
|||
<key>Value</key>
|
||||
<integer>4</integer>
|
||||
</map>
|
||||
<key>FSLastSnapshotToPrimfeedHeight</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>The height of the last Primfeed snapshot, in px</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>S32</string>
|
||||
<key>Value</key>
|
||||
<integer>768</integer>
|
||||
</map>
|
||||
<key>FSLastSnapshotToPrimfeedWidth</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>The width of the last Primfeed snapshot, in px</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>S32</string>
|
||||
<key>Value</key>
|
||||
<integer>1024</integer>
|
||||
</map>
|
||||
<key>FSLastSnapshotToPrimfeedResolution</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>At what resolution should snapshots be posted on Primfeed. 0=Current Window, 1=320x240, 2=640x480, 3=800x600, 4=1024x768, 5=1280x1024, 6=1600x1200, 7=Custom</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>S32</string>
|
||||
<key>Value</key>
|
||||
<integer>4</integer>
|
||||
</map>
|
||||
<key>FSLastSnapshotToTwitterHeight</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
|
|
|
|||
|
|
@ -1358,6 +1358,94 @@
|
|||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>FSPrimfeedOAuthToken</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>contains the secure authentication toke to post to your primfeed account (do not share)</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>String</string>
|
||||
<key>Value</key>
|
||||
<string></string>
|
||||
</map>
|
||||
<key>FSPrimfeedProfileLink</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>The profile page for the account associated with the currently linked Primfeed account</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>String</string>
|
||||
<key>Value</key>
|
||||
<string></string>
|
||||
</map>
|
||||
<key>FSPrimfeedPlan</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>The plan type associated with the currently linked Primfeed account</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>String</string>
|
||||
<key>Value</key>
|
||||
<string></string>
|
||||
</map>
|
||||
<key>FSPrimfeedUsername</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>The username associated with the currently linked Primfeed account</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>String</string>
|
||||
<key>Value</key>
|
||||
<string></string>
|
||||
</map>
|
||||
<key>FSPrimfeedCommercialContent</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Does this post contain commercial content</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>FSPrimfeedAddToPublicGallery</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Should this post go to the public gallery?</string>
|
||||
<key>Persist</key>
|
||||
<integer>0</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>FSPrimfeedAddToPublicGallery</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Should this post go to the public gallery?</string>
|
||||
<key>Persist</key>
|
||||
<integer>0</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>FSPrimfeedOpenURLOnPost</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>if true open the URL in a browser when the post completes</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>FSProtectedFolders</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
|
|
@ -1369,5 +1457,16 @@
|
|||
<key>Value</key>
|
||||
<array/>
|
||||
</map>
|
||||
<key>FSPrimfeedPhotoRating</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Content rating to be shared with Primfeed.</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Integer</string>
|
||||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
</map>
|
||||
</llsd>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,910 @@
|
|||
/**
|
||||
* @file fsfloaterprimfeed.cpp
|
||||
* @brief Implementation of primfeed floater
|
||||
* @author beq@firestorm
|
||||
*
|
||||
* $LicenseInfo:firstyear=2025&license=fsviewerlgpl$
|
||||
* Phoenix Firestorm Viewer Source Code
|
||||
* Copyright (C) 2025, Beq Janus
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
|
||||
* http://www.firestormviewer.org
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#include "llviewerprecompiledheaders.h"
|
||||
|
||||
#include "fsfloaterprimfeed.h"
|
||||
#include "fsprimfeedconnect.h"
|
||||
#include "llagent.h"
|
||||
#include "llagentui.h"
|
||||
#include "llcheckboxctrl.h"
|
||||
#include "llcombobox.h"
|
||||
#include "llfloaterreg.h"
|
||||
#include "lliconctrl.h"
|
||||
#include "llimagefiltersmanager.h"
|
||||
#include "llresmgr.h" // LLLocale
|
||||
#include "llsdserialize.h"
|
||||
#include "llloadingindicator.h"
|
||||
#include "llslurl.h"
|
||||
#include "lltrans.h"
|
||||
#include "llfloatersnapshot.h"
|
||||
#include "llsnapshotlivepreview.h"
|
||||
#include "llfloaterbigpreview.h"
|
||||
#include "llviewerregion.h"
|
||||
#include "llviewercontrol.h"
|
||||
#include "llviewermedia.h"
|
||||
#include "lltabcontainer.h"
|
||||
#include "llviewerparcelmgr.h"
|
||||
#include "llviewerregion.h"
|
||||
#include <boost/regex.hpp>
|
||||
#include "llspinctrl.h"
|
||||
|
||||
#include "llviewernetwork.h"
|
||||
#include "llnotificationsutil.h"
|
||||
#include "fsprimfeedauth.h"
|
||||
#include "llviewernetwork.h"
|
||||
|
||||
static LLPanelInjector<FSPrimfeedPhotoPanel> t_panel_photo("fsprimfeedphotopanel");
|
||||
static LLPanelInjector<FSPrimfeedAccountPanel> t_panel_account("fsprimfeedaccountpanel");
|
||||
|
||||
///////////////////////////
|
||||
//FSPrimfeedPhotoPanel/////
|
||||
///////////////////////////
|
||||
|
||||
FSPrimfeedPhotoPanel::FSPrimfeedPhotoPanel() :
|
||||
mResolutionComboBox(nullptr),
|
||||
mRefreshBtn(nullptr),
|
||||
mBtnPreview(nullptr),
|
||||
mWorkingLabel(nullptr),
|
||||
mThumbnailPlaceholder(nullptr),
|
||||
mDescriptionTextBox(nullptr),
|
||||
mLocationCheckbox(nullptr),
|
||||
mRatingComboBox(nullptr),
|
||||
mBigPreviewFloater(nullptr),
|
||||
mPostButton(nullptr)
|
||||
{
|
||||
mCommitCallbackRegistrar.add("SocialSharing.SendPhoto", [this](LLUICtrl* ctrl, const LLSD& data) { onSend(); });
|
||||
mCommitCallbackRegistrar.add("SocialSharing.RefreshPhoto", [this](LLUICtrl* ctrl, const LLSD& data) { onClickNewSnapshot(); });
|
||||
mCommitCallbackRegistrar.add("SocialSharing.BigPreview", [this](LLUICtrl* ctrl, const LLSD& data) { onClickBigPreview(); });
|
||||
mCommitCallbackRegistrar.add("Primfeed.Info", [this](LLUICtrl* ctrl, const LLSD& param) {
|
||||
const std::string url = param.asString();
|
||||
LL_DEBUGS("primfeed") << "Info button clicked, opening " << url << LL_ENDL;
|
||||
LLWeb::loadURLExternal(url);
|
||||
});
|
||||
}
|
||||
|
||||
FSPrimfeedPhotoPanel::~FSPrimfeedPhotoPanel()
|
||||
{
|
||||
if(mPreviewHandle.get())
|
||||
{
|
||||
mPreviewHandle.get()->die();
|
||||
}
|
||||
|
||||
FSPrimfeedAuth::sPrimfeedAuthPump->stopListening("FSPrimfeedAccountPanel");
|
||||
|
||||
gSavedSettings.setS32("FSLastSnapshotToPrimfeedResolution", getChild<LLComboBox>("resolution_combobox")->getCurrentIndex());
|
||||
gSavedSettings.setS32("FSLastSnapshotToPrimfeedWidth", getChild<LLSpinCtrl>("custom_snapshot_width")->getValue().asInteger());
|
||||
gSavedSettings.setS32("FSLastSnapshotToPrimfeedHeight", getChild<LLSpinCtrl>("custom_snapshot_height")->getValue().asInteger());
|
||||
}
|
||||
|
||||
bool FSPrimfeedPhotoPanel::postBuild()
|
||||
{
|
||||
setVisibleCallback([this](LLUICtrl * unused, bool visible) {
|
||||
onVisibilityChange(visible);
|
||||
});
|
||||
|
||||
mResolutionComboBox = getChild<LLUICtrl>("resolution_combobox");
|
||||
mResolutionComboBox->setCommitCallback([this](LLUICtrl *, const LLSD&) { updateResolution(true); });
|
||||
mFilterComboBox = getChild<LLUICtrl>("filters_combobox");
|
||||
mFilterComboBox->setCommitCallback([this](LLUICtrl *, const LLSD&) { updateResolution(true); });
|
||||
mRefreshBtn = getChild<LLUICtrl>("new_snapshot_btn");
|
||||
mBtnPreview = getChild<LLButton>("big_preview_btn");
|
||||
mWorkingLabel = getChild<LLUICtrl>("working_lbl");
|
||||
mThumbnailPlaceholder = getChild<LLUICtrl>("thumbnail_placeholder");
|
||||
mDescriptionTextBox = getChild<LLUICtrl>("photo_description");
|
||||
mLocationCheckbox = getChild<LLUICtrl>("add_location_cb");
|
||||
mCommercialCheckbox = getChild<LLUICtrl>("primfeed_commercial_content");
|
||||
mPublicGalleryCheckbox = getChild<LLUICtrl>("primfeed_add_to_public_gallery");
|
||||
mRatingComboBox = getChild<LLUICtrl>("rating_combobox");
|
||||
mPostButton = getChild<LLUICtrl>("post_photo_btn");
|
||||
mCancelButton = getChild<LLUICtrl>("cancel_photo_btn");
|
||||
mBigPreviewFloater = dynamic_cast<LLFloaterBigPreview*>(LLFloaterReg::getInstance("big_preview"));
|
||||
|
||||
// Update custom resolution controls with lambdas
|
||||
getChild<LLSpinCtrl>("custom_snapshot_width")->setCommitCallback([this](LLUICtrl *, const LLSD&) { updateResolution(true); });
|
||||
getChild<LLSpinCtrl>("custom_snapshot_height")->setCommitCallback([this](LLUICtrl *, const LLSD&) { updateResolution(true); });
|
||||
getChild<LLCheckBoxCtrl>("keep_aspect_ratio")->setCommitCallback([this](LLUICtrl *, const LLSD&) { updateResolution(true); });
|
||||
|
||||
getChild<LLComboBox>("resolution_combobox")->setCurrentByIndex(gSavedSettings.getS32("FSLastSnapshotToPrimfeedResolution"));
|
||||
getChild<LLSpinCtrl>("custom_snapshot_width")->setValue(gSavedSettings.getS32("FSLastSnapshotToPrimfeedWidth"));
|
||||
getChild<LLSpinCtrl>("custom_snapshot_height")->setValue(gSavedSettings.getS32("FSLastSnapshotToPrimfeedHeight"));
|
||||
|
||||
// Update filter list
|
||||
std::vector<std::string> filter_list = LLImageFiltersManager::getInstance()->getFiltersList();
|
||||
LLComboBox* filterbox = static_cast<LLComboBox *>(mFilterComboBox);
|
||||
for (U32 i = 0; i < filter_list.size(); i++)
|
||||
{
|
||||
filterbox->add(filter_list[i]);
|
||||
}
|
||||
|
||||
return LLPanel::postBuild();
|
||||
}
|
||||
|
||||
//static
|
||||
void FSFloaterPrimfeed::update()
|
||||
{
|
||||
if (LLFloaterReg::instanceVisible("primfeed"))
|
||||
{
|
||||
LLFloaterSnapshotBase::ImplBase::updatePreviewList( true, true );
|
||||
}
|
||||
}
|
||||
|
||||
// virtual
|
||||
S32 FSPrimfeedPhotoPanel::notify(const LLSD& info)
|
||||
{
|
||||
if (info.has("snapshot-updating"))
|
||||
{
|
||||
// Disable the Post button and whatever else while the snapshot is not updated
|
||||
// updateControls();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (info.has("snapshot-updated"))
|
||||
{
|
||||
// Enable the send/post/save buttons.
|
||||
updateControls();
|
||||
|
||||
// The refresh button is initially hidden. We show it after the first update,
|
||||
// i.e. after snapshot is taken
|
||||
LLUICtrl * refresh_button = getRefreshBtn();
|
||||
if (!refresh_button->getVisible())
|
||||
{
|
||||
refresh_button->setVisible(true);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void FSPrimfeedPhotoPanel::draw()
|
||||
{
|
||||
LLSnapshotLivePreview * previewp = static_cast<LLSnapshotLivePreview *>(mPreviewHandle.get());
|
||||
|
||||
// Enable interaction only if no transaction with the service is on-going (prevent duplicated posts)
|
||||
auto can_post = !(FSPrimfeedConnect::instance().isTransactionOngoing()) && FSPrimfeedAuth::isAuthorized();
|
||||
|
||||
mCancelButton->setEnabled(can_post);
|
||||
mDescriptionTextBox->setEnabled(can_post);
|
||||
mRatingComboBox->setEnabled(can_post);
|
||||
mResolutionComboBox->setEnabled(can_post);
|
||||
mFilterComboBox->setEnabled(can_post);
|
||||
mRefreshBtn->setEnabled(can_post);
|
||||
mBtnPreview->setEnabled(can_post);
|
||||
mLocationCheckbox->setEnabled(can_post);
|
||||
|
||||
// Reassign the preview floater if we have the focus and the preview exists
|
||||
if (hasFocus() && isPreviewVisible())
|
||||
{
|
||||
attachPreview();
|
||||
}
|
||||
|
||||
// Toggle the button state as appropriate
|
||||
bool preview_active = (isPreviewVisible() && mBigPreviewFloater->isFloaterOwner(getParentByType<LLFloater>()));
|
||||
mBtnPreview->setToggleState(preview_active);
|
||||
|
||||
// Display the preview if one is available
|
||||
if (previewp && previewp->getThumbnailImage())
|
||||
{
|
||||
const LLRect& thumbnail_rect = mThumbnailPlaceholder->getRect();
|
||||
const S32 thumbnail_w = previewp->getThumbnailWidth();
|
||||
const S32 thumbnail_h = previewp->getThumbnailHeight();
|
||||
|
||||
// calc preview offset within the preview rect
|
||||
const S32 local_offset_x = (thumbnail_rect.getWidth() - thumbnail_w) / 2 ;
|
||||
const S32 local_offset_y = (thumbnail_rect.getHeight() - thumbnail_h) / 2 ;
|
||||
S32 offset_x = thumbnail_rect.mLeft + local_offset_x;
|
||||
S32 offset_y = thumbnail_rect.mBottom + local_offset_y;
|
||||
|
||||
gGL.matrixMode(LLRender::MM_MODELVIEW);
|
||||
// Apply floater transparency to the texture unless the floater is focused.
|
||||
F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency();
|
||||
LLColor4 color = LLColor4::white;
|
||||
gl_draw_scaled_image(offset_x, offset_y,
|
||||
thumbnail_w, thumbnail_h,
|
||||
previewp->getThumbnailImage(), color % alpha);
|
||||
}
|
||||
|
||||
// Update the visibility of the working (computing preview) label
|
||||
mWorkingLabel->setVisible(!(previewp && previewp->getSnapshotUpToDate()));
|
||||
|
||||
// Enable Post if we have a preview to send and no on going connection being processed
|
||||
mPostButton->setEnabled(can_post && (previewp && previewp->getSnapshotUpToDate()));
|
||||
|
||||
// Draw the rest of the panel on top of it
|
||||
LLPanel::draw();
|
||||
}
|
||||
|
||||
LLSnapshotLivePreview* FSPrimfeedPhotoPanel::getPreviewView()
|
||||
{
|
||||
LLSnapshotLivePreview* previewp = (LLSnapshotLivePreview*)mPreviewHandle.get();
|
||||
return previewp;
|
||||
}
|
||||
|
||||
void FSPrimfeedPhotoPanel::onVisibilityChange(bool visible)
|
||||
{
|
||||
if (visible)
|
||||
{
|
||||
if (mPreviewHandle.get())
|
||||
{
|
||||
LLSnapshotLivePreview* preview = getPreviewView();
|
||||
if(preview)
|
||||
{
|
||||
LL_DEBUGS() << "opened, updating snapshot" << LL_ENDL;
|
||||
preview->updateSnapshot(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LLRect full_screen_rect = getRootView()->getRect();
|
||||
LLSnapshotLivePreview::Params p;
|
||||
p.rect(full_screen_rect);
|
||||
LLSnapshotLivePreview* previewp = new LLSnapshotLivePreview(p);
|
||||
mPreviewHandle = previewp->getHandle();
|
||||
|
||||
previewp->setContainer(this);
|
||||
previewp->setSnapshotType(LLSnapshotModel::SNAPSHOT_WEB);
|
||||
previewp->setSnapshotFormat(LLSnapshotModel::SNAPSHOT_FORMAT_PNG);
|
||||
previewp->setThumbnailSubsampled(true); // We want the preview to reflect the *saved* image
|
||||
previewp->setAllowRenderUI(false); // We do not want the rendered UI in our snapshots
|
||||
previewp->setAllowFullScreenPreview(false); // No full screen preview in SL Share mode
|
||||
previewp->setThumbnailPlaceholderRect(mThumbnailPlaceholder->getRect());
|
||||
|
||||
updateControls();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FSPrimfeedPhotoPanel::onClickNewSnapshot()
|
||||
{
|
||||
LLSnapshotLivePreview* previewp = getPreviewView();
|
||||
if (previewp)
|
||||
{
|
||||
previewp->updateSnapshot(true);
|
||||
}
|
||||
}
|
||||
|
||||
void FSPrimfeedPhotoPanel::onClickBigPreview()
|
||||
{
|
||||
// Toggle the preview
|
||||
if (isPreviewVisible())
|
||||
{
|
||||
LLFloaterReg::hideInstance("big_preview");
|
||||
}
|
||||
else
|
||||
{
|
||||
attachPreview();
|
||||
LLFloaterReg::showInstance("big_preview");
|
||||
}
|
||||
}
|
||||
|
||||
bool FSPrimfeedPhotoPanel::isPreviewVisible()
|
||||
{
|
||||
return (mBigPreviewFloater && mBigPreviewFloater->getVisible());
|
||||
}
|
||||
|
||||
void FSPrimfeedPhotoPanel::attachPreview()
|
||||
{
|
||||
if (mBigPreviewFloater)
|
||||
{
|
||||
LLSnapshotLivePreview* previewp = getPreviewView();
|
||||
mBigPreviewFloater->setPreview(previewp);
|
||||
mBigPreviewFloater->setFloaterOwner(getParentByType<LLFloater>());
|
||||
}
|
||||
}
|
||||
|
||||
void FSPrimfeedPhotoPanel::onSend()
|
||||
{
|
||||
sendPhoto();
|
||||
}
|
||||
|
||||
bool FSPrimfeedPhotoPanel::onPrimfeedConnectStateChange(const LLSD& data)
|
||||
{
|
||||
if (FSPrimfeedAuth::isAuthorized())
|
||||
{
|
||||
sendPhoto();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FSPrimfeedPhotoPanel::sendPhoto()
|
||||
{
|
||||
static const std::array<std::string,4> RATING_NAMES = {
|
||||
"general", // 1
|
||||
"moderate", // 2
|
||||
"adult", // 3
|
||||
"adult_plus" // 4
|
||||
};
|
||||
|
||||
auto ratingToString = [&](int rating) -> std::string {
|
||||
// clamp into [1,4]
|
||||
int idx = llclamp(rating, 1, 4) - 1;
|
||||
return RATING_NAMES[idx];
|
||||
};
|
||||
// Get the description (primfeed has no title/tags etc at this point)
|
||||
std::string description = mDescriptionTextBox->getValue().asString();
|
||||
|
||||
|
||||
// Get the content rating
|
||||
int content_rating = mRatingComboBox->getValue().asInteger();
|
||||
bool post_to_public_gallery = mPublicGalleryCheckbox->getValue().asBoolean();
|
||||
bool commercial_content = mCommercialCheckbox->getValue().asBoolean();
|
||||
|
||||
// Get the image
|
||||
LLSnapshotLivePreview* previewp = getPreviewView();
|
||||
|
||||
FSPrimfeedConnect::instance().setConnectionState(FSPrimfeedConnect::PRIMFEED_POSTING);
|
||||
LLSD params;
|
||||
params["rating"] = ratingToString(content_rating);
|
||||
params["content"] = description;
|
||||
params["is_commercial"] = commercial_content;
|
||||
params["post_to_public_gallery"] = post_to_public_gallery;
|
||||
// Add the location if required
|
||||
bool add_location = mLocationCheckbox->getValue().asBoolean();
|
||||
if (add_location)
|
||||
{
|
||||
// Get the SLURL for the location
|
||||
LLSLURL slurl;
|
||||
LLAgentUI::buildSLURL(slurl);
|
||||
std::string slurl_string = slurl.getSLURLString();
|
||||
|
||||
params["location"] = slurl_string;
|
||||
}
|
||||
|
||||
FSPrimfeedConnect::instance().uploadPhoto(params, previewp->getFormattedImage().get(),
|
||||
[this](bool success, const std::string& url)
|
||||
{
|
||||
if (success)
|
||||
{
|
||||
FSPrimfeedConnect::instance().setConnectionState(FSPrimfeedConnect::PRIMFEED_POSTED);
|
||||
static LLCachedControl<bool> open_url_on_post(gSavedPerAccountSettings, "FSPrimfeedOpenURLOnPost", true);
|
||||
if (open_url_on_post)
|
||||
{
|
||||
LLWeb::loadURLExternal(url);
|
||||
}
|
||||
LLSD args;
|
||||
args["PF_POSTURL"] = url;
|
||||
LLNotificationsUtil::add("FSPrimfeedUploadComplete", args);
|
||||
}
|
||||
else
|
||||
{
|
||||
mWorkingLabel->setValue("Error posting to Primfeed");
|
||||
mPostButton->setEnabled(true);
|
||||
}
|
||||
}
|
||||
);
|
||||
updateControls();
|
||||
}
|
||||
|
||||
void FSPrimfeedPhotoPanel::clearAndClose()
|
||||
{
|
||||
mDescriptionTextBox->setValue("");
|
||||
|
||||
LLFloater* floater = getParentByType<LLFloater>();
|
||||
if (floater)
|
||||
{
|
||||
floater->closeFloater();
|
||||
if (mBigPreviewFloater)
|
||||
{
|
||||
mBigPreviewFloater->closeOnFloaterOwnerClosing(floater);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FSPrimfeedPhotoPanel::updateControls()
|
||||
{
|
||||
LLSnapshotLivePreview* previewp = getPreviewView();
|
||||
bool got_snap = previewp && previewp->getSnapshotUpToDate();
|
||||
|
||||
updateResolution(false);
|
||||
}
|
||||
|
||||
void FSPrimfeedPhotoPanel::updateResolution(bool do_update)
|
||||
{
|
||||
LLComboBox* combobox = static_cast<LLComboBox *>(mResolutionComboBox);
|
||||
LLComboBox* filterbox = static_cast<LLComboBox *>(mFilterComboBox);
|
||||
|
||||
std::string sdstring = combobox->getSelectedValue();
|
||||
LLSD sdres;
|
||||
std::stringstream sstream(sdstring);
|
||||
LLSDSerialize::fromNotation(sdres, sstream, sdstring.size());
|
||||
|
||||
S32 width = sdres[0];
|
||||
S32 height = sdres[1];
|
||||
|
||||
// Note : index 0 of the filter drop down is assumed to be "No filter" in whichever locale
|
||||
std::string filter_name = (filterbox->getCurrentIndex() ? filterbox->getSimple() : "");
|
||||
|
||||
LLSnapshotLivePreview * previewp = static_cast<LLSnapshotLivePreview *>(mPreviewHandle.get());
|
||||
if (previewp && combobox->getCurrentIndex() >= 0)
|
||||
{
|
||||
checkAspectRatio(width);
|
||||
|
||||
S32 original_width = 0 , original_height = 0 ;
|
||||
previewp->getSize(original_width, original_height) ;
|
||||
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
// take resolution from current window size
|
||||
LL_DEBUGS() << "Setting preview res from window: " << gViewerWindow->getWindowWidthRaw() << "x" << gViewerWindow->getWindowHeightRaw() << LL_ENDL;
|
||||
previewp->setSize(gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw());
|
||||
}
|
||||
else if (width == -1 || height == -1)
|
||||
{
|
||||
// take resolution from custom size
|
||||
LLSpinCtrl* width_spinner = getChild<LLSpinCtrl>("custom_snapshot_width");
|
||||
LLSpinCtrl* height_spinner = getChild<LLSpinCtrl>("custom_snapshot_height");
|
||||
S32 custom_width = width_spinner->getValue().asInteger();
|
||||
S32 custom_height = height_spinner->getValue().asInteger();
|
||||
if (checkImageSize(previewp, custom_width, custom_height, true, previewp->getMaxImageSize()))
|
||||
{
|
||||
width_spinner->set((F32)custom_width);
|
||||
height_spinner->set((F32)custom_height);
|
||||
}
|
||||
LL_DEBUGS() << "Setting preview res from custom: " << custom_width << "x" << custom_height << LL_ENDL;
|
||||
previewp->setSize(custom_width, custom_height);
|
||||
}
|
||||
else
|
||||
{
|
||||
// use the resolution from the selected pre-canned drop-down choice
|
||||
LL_DEBUGS() << "Setting preview res selected from combo: " << width << "x" << height << LL_ENDL;
|
||||
previewp->setSize(width, height);
|
||||
}
|
||||
|
||||
|
||||
previewp->getSize(width, height);
|
||||
if ((original_width != width) || (original_height != height))
|
||||
{
|
||||
previewp->setSize(width, height);
|
||||
if (do_update)
|
||||
{
|
||||
previewp->updateSnapshot(true, true);
|
||||
updateControls();
|
||||
}
|
||||
}
|
||||
// Get the old filter, compare to the current one "filter_name" and set if changed
|
||||
std::string original_filter = previewp->getFilter();
|
||||
if (original_filter != filter_name)
|
||||
{
|
||||
previewp->setFilter(filter_name);
|
||||
if (do_update)
|
||||
{
|
||||
previewp->updateSnapshot(false, true);
|
||||
updateControls();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool custom_resolution = static_cast<LLComboBox *>(mResolutionComboBox)->getSelectedValue().asString() == "[i-1,i-1]";
|
||||
getChild<LLSpinCtrl>("custom_snapshot_width")->setEnabled(custom_resolution);
|
||||
getChild<LLSpinCtrl>("custom_snapshot_height")->setEnabled(custom_resolution);
|
||||
getChild<LLCheckBoxCtrl>("keep_aspect_ratio")->setEnabled(custom_resolution);
|
||||
}
|
||||
|
||||
void FSPrimfeedPhotoPanel::checkAspectRatio(S32 index)
|
||||
{
|
||||
LLSnapshotLivePreview *previewp = getPreviewView() ;
|
||||
|
||||
bool keep_aspect = false;
|
||||
|
||||
if (0 == index) // current window size
|
||||
{
|
||||
keep_aspect = true;
|
||||
}
|
||||
else if (-1 == index)
|
||||
{
|
||||
keep_aspect = getChild<LLCheckBoxCtrl>("keep_aspect_ratio")->get();
|
||||
}
|
||||
else // predefined resolution
|
||||
{
|
||||
keep_aspect = false;
|
||||
}
|
||||
|
||||
if (previewp)
|
||||
{
|
||||
previewp->mKeepAspectRatio = keep_aspect;
|
||||
}
|
||||
}
|
||||
|
||||
LLUICtrl* FSPrimfeedPhotoPanel::getRefreshBtn()
|
||||
{
|
||||
return mRefreshBtn;
|
||||
}
|
||||
|
||||
void FSPrimfeedPhotoPanel::onOpen(const LLSD& key)
|
||||
{
|
||||
if (!FSPrimfeedAuth::isAuthorized())
|
||||
{
|
||||
// Reauthorise if necessary.
|
||||
FSPrimfeedAuth::initiateAuthRequest();
|
||||
}
|
||||
}
|
||||
|
||||
void FSPrimfeedPhotoPanel::uploadCallback(bool success, const LLSD& response)
|
||||
{
|
||||
LLSD args;
|
||||
if(success && response["stat"].asString() == "ok")
|
||||
{
|
||||
FSPrimfeedConnect::instance().setConnectionState(FSPrimfeedConnect::PRIMFEED_POSTED);
|
||||
args["PF_POSTURL"] = response["postUrl"];
|
||||
LLNotificationsUtil::add("FSPrimfeedUploadComplete", args);
|
||||
}
|
||||
else
|
||||
{
|
||||
FSPrimfeedConnect::instance().setConnectionState(FSPrimfeedConnect::PRIMFEED_POST_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
void FSPrimfeedPhotoPanel::primfeedAuthResponse(bool success, const LLSD& response)
|
||||
{
|
||||
if(!success)
|
||||
{
|
||||
if(response.has("status") && response["status"].asString() == "reset")
|
||||
{
|
||||
LL_INFOS("Primfeed") << "Primfeed authorization has been reset." << LL_ENDL;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Complain about failed auth here.
|
||||
LL_WARNS("Primfeed") << "Primfeed authentication failed." << LL_ENDL;
|
||||
}
|
||||
}
|
||||
onPrimfeedConnectStateChange(response);
|
||||
}
|
||||
|
||||
bool FSPrimfeedPhotoPanel::checkImageSize(LLSnapshotLivePreview* previewp, S32& width, S32& height, bool isWidthChanged, S32 max_value)
|
||||
{
|
||||
S32 w = width ;
|
||||
S32 h = height ;
|
||||
|
||||
if(previewp && previewp->mKeepAspectRatio)
|
||||
{
|
||||
if(gViewerWindow->getWindowWidthRaw() < 1 || gViewerWindow->getWindowHeightRaw() < 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//aspect ratio of the current window
|
||||
F32 aspect_ratio = (F32)gViewerWindow->getWindowWidthRaw() / gViewerWindow->getWindowHeightRaw() ;
|
||||
|
||||
//change another value proportionally
|
||||
if(isWidthChanged)
|
||||
{
|
||||
height = ll_round(width / aspect_ratio) ;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = ll_round(height * aspect_ratio) ;
|
||||
}
|
||||
|
||||
//bound w/h by the max_value
|
||||
if(width > max_value || height > max_value)
|
||||
{
|
||||
if(width > height)
|
||||
{
|
||||
width = max_value ;
|
||||
height = (S32)(width / aspect_ratio) ;
|
||||
}
|
||||
else
|
||||
{
|
||||
height = max_value ;
|
||||
width = (S32)(height * aspect_ratio) ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (w != width || h != height) ;
|
||||
}
|
||||
|
||||
///////////////////////////
|
||||
//FSPrimfeedAccountPanel///
|
||||
///////////////////////////
|
||||
|
||||
FSPrimfeedAccountPanel::FSPrimfeedAccountPanel() :
|
||||
mAccountConnectedAsLabel(nullptr),
|
||||
mAccountNameLink(nullptr),
|
||||
mAccountPlan(nullptr),
|
||||
mPanelButtons(nullptr),
|
||||
mConnectButton(nullptr),
|
||||
mDisconnectButton(nullptr)
|
||||
{
|
||||
mCommitCallbackRegistrar.add("SocialSharing.Connect", [this](LLUICtrl* ctrl, const LLSD& data) { onConnect(); });
|
||||
mCommitCallbackRegistrar.add("SocialSharing.Disconnect", [this](LLUICtrl* ctrl, const LLSD& data) { onDisconnect(); });
|
||||
|
||||
FSPrimfeedAuth::sPrimfeedAuthPump->listen("FSPrimfeedAccountPanel",
|
||||
[this](const LLSD& data) -> bool
|
||||
{
|
||||
bool success = data["success"].asBoolean();
|
||||
this->primfeedAuthResponse(success, data);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
setVisibleCallback([this](LLUICtrl *unused, bool visible) {
|
||||
onVisibilityChange(visible);
|
||||
});
|
||||
}
|
||||
|
||||
bool FSPrimfeedAccountPanel::postBuild()
|
||||
{
|
||||
mAccountConnectedAsLabel= getChild<LLTextBox>("connected_as_label");
|
||||
mAccountNameLink = getChild<LLTextBox>("primfeed_account_name");
|
||||
mAccountPlan = getChild<LLTextBox>("primfeed_account_plan");
|
||||
mPanelButtons = getChild<LLUICtrl>("panel_buttons");
|
||||
mConnectButton = getChild<LLUICtrl>("connect_btn");
|
||||
mDisconnectButton = getChild<LLUICtrl>("disconnect_btn");
|
||||
|
||||
LLSD dummy;
|
||||
onPrimfeedConnectStateChange(dummy);
|
||||
return LLPanel::postBuild();
|
||||
}
|
||||
|
||||
void FSPrimfeedAccountPanel::draw()
|
||||
{
|
||||
FSPrimfeedConnect::EConnectionState connection_state = FSPrimfeedConnect::instance().getConnectionState();
|
||||
|
||||
//Disable the 'disconnect' button and the 'use another account' button when disconnecting in progress
|
||||
bool disconnecting = connection_state == FSPrimfeedConnect::PRIMFEED_DISCONNECTING;
|
||||
mDisconnectButton->setEnabled(!disconnecting);
|
||||
|
||||
//Disable the 'connect' button when a connection is in progress
|
||||
bool connecting = ( connection_state == FSPrimfeedConnect::PRIMFEED_CONNECTING ||
|
||||
connection_state == FSPrimfeedConnect::PRIMFEED_CONNECTED );
|
||||
mConnectButton->setEnabled(!connecting);
|
||||
|
||||
LLPanel::draw();
|
||||
}
|
||||
|
||||
void FSPrimfeedAccountPanel::primfeedAuthResponse(bool success, const LLSD& response)
|
||||
{
|
||||
if(!success)
|
||||
{
|
||||
LL_WARNS("Primfeed") << "Primfeed authentication failed." << LL_ENDL;
|
||||
LLWeb::loadURLExternal("https://www.primfeed.com/login");
|
||||
}
|
||||
onPrimfeedConnectStateChange(response);
|
||||
}
|
||||
|
||||
void FSPrimfeedAccountPanel::onVisibilityChange(bool visible)
|
||||
{
|
||||
if(visible)
|
||||
{
|
||||
|
||||
|
||||
//Connected
|
||||
if(FSPrimfeedAuth::isAuthorized())
|
||||
{
|
||||
showConnectedLayout();
|
||||
}
|
||||
else
|
||||
{
|
||||
showDisconnectedLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FSPrimfeedAccountPanel::onPrimfeedConnectStateChange(const LLSD& data)
|
||||
{
|
||||
if (FSPrimfeedAuth::isAuthorized())
|
||||
{
|
||||
showConnectedLayout();
|
||||
}
|
||||
else
|
||||
{
|
||||
showDisconnectedLayout();
|
||||
}
|
||||
onPrimfeedConnectInfoChange();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FSPrimfeedAccountPanel::onPrimfeedConnectInfoChange()
|
||||
{
|
||||
std::string clickable_name{""};
|
||||
|
||||
static LLCachedControl<std::string> primfeed_username(gSavedPerAccountSettings, "FSPrimfeedUsername");
|
||||
static LLCachedControl<std::string> primfeed_profile_link(gSavedPerAccountSettings, "FSPrimfeedProfileLink");
|
||||
static LLCachedControl<std::string> primfeed_plan(gSavedPerAccountSettings, "FSPrimfeedPlan");
|
||||
|
||||
//Strings of format [http://www.somewebsite.com Click Me] become clickable text
|
||||
if (!primfeed_username().empty())
|
||||
{
|
||||
clickable_name = std::string("[") + std::string(primfeed_profile_link) + " " + std::string(primfeed_username) + "]";
|
||||
}
|
||||
|
||||
mAccountNameLink->setText(clickable_name);
|
||||
mAccountPlan->setText(primfeed_plan());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FSPrimfeedAccountPanel::showConnectButton()
|
||||
{
|
||||
if(!mConnectButton->getVisible())
|
||||
{
|
||||
mConnectButton->setVisible(true);
|
||||
mDisconnectButton->setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
void FSPrimfeedAccountPanel::hideConnectButton()
|
||||
{
|
||||
if(mConnectButton->getVisible())
|
||||
{
|
||||
mConnectButton->setVisible(false);
|
||||
mDisconnectButton->setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
void FSPrimfeedAccountPanel::showDisconnectedLayout()
|
||||
{
|
||||
mAccountConnectedAsLabel->setText(getString("primfeed_disconnected"));
|
||||
mAccountNameLink->setText(std::string(""));
|
||||
mAccountPlan->setText(getString("primfeed_plan_unknown"));
|
||||
showConnectButton();
|
||||
}
|
||||
|
||||
void FSPrimfeedAccountPanel::showConnectedLayout()
|
||||
{
|
||||
mAccountConnectedAsLabel->setText(getString("primfeed_connected"));
|
||||
hideConnectButton();
|
||||
}
|
||||
|
||||
void FSPrimfeedAccountPanel::onConnect()
|
||||
{
|
||||
FSPrimfeedAuth::initiateAuthRequest();
|
||||
}
|
||||
|
||||
void FSPrimfeedAccountPanel::onDisconnect()
|
||||
{
|
||||
FSPrimfeedAuth::resetAuthStatus();
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
//FSFloaterPrimfeed/////
|
||||
////////////////////////
|
||||
|
||||
FSFloaterPrimfeed::FSFloaterPrimfeed(const LLSD& key) : LLFloater(key),
|
||||
mPrimfeedPhotoPanel(nullptr),
|
||||
mStatusErrorText(nullptr),
|
||||
mStatusLoadingText(nullptr),
|
||||
mStatusLoadingIndicator(nullptr)
|
||||
{
|
||||
mCommitCallbackRegistrar.add("SocialSharing.Cancel", [this](LLUICtrl* ctrl, const LLSD& data) { onCancel(); });
|
||||
}
|
||||
|
||||
void FSFloaterPrimfeed::onClose(bool app_quitting)
|
||||
{
|
||||
LLFloaterBigPreview* big_preview_floater = dynamic_cast<LLFloaterBigPreview*>(LLFloaterReg::getInstance("big_preview"));
|
||||
if (big_preview_floater)
|
||||
{
|
||||
big_preview_floater->closeOnFloaterOwnerClosing(this);
|
||||
}
|
||||
LLFloater::onClose(app_quitting);
|
||||
}
|
||||
|
||||
void FSFloaterPrimfeed::onCancel()
|
||||
{
|
||||
LLFloaterBigPreview* big_preview_floater = dynamic_cast<LLFloaterBigPreview*>(LLFloaterReg::getInstance("big_preview"));
|
||||
if (big_preview_floater)
|
||||
{
|
||||
big_preview_floater->closeOnFloaterOwnerClosing(this);
|
||||
}
|
||||
closeFloater();
|
||||
}
|
||||
|
||||
bool FSFloaterPrimfeed::postBuild()
|
||||
{
|
||||
// Keep tab of the Photo Panel
|
||||
mPrimfeedPhotoPanel = static_cast<FSPrimfeedPhotoPanel*>(getChild<LLUICtrl>("panel_primfeed_photo"));
|
||||
mPrimfeedAccountPanel = static_cast<FSPrimfeedAccountPanel*>(getChild<LLUICtrl>("panel_primfeed_account"));
|
||||
// Connection status widgets
|
||||
mStatusErrorText = getChild<LLTextBox>("connection_error_text");
|
||||
mStatusLoadingText = getChild<LLTextBox>("connection_loading_text");
|
||||
mStatusLoadingIndicator = getChild<LLUICtrl>("connection_loading_indicator");
|
||||
|
||||
return LLFloater::postBuild();
|
||||
}
|
||||
|
||||
void FSFloaterPrimfeed::showPhotoPanel()
|
||||
{
|
||||
LLTabContainer* parent = dynamic_cast<LLTabContainer*>(mPrimfeedPhotoPanel->getParent());
|
||||
if (!parent)
|
||||
{
|
||||
LL_WARNS() << "Cannot find panel container" << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
|
||||
parent->selectTabPanel(mPrimfeedPhotoPanel);
|
||||
}
|
||||
|
||||
void FSFloaterPrimfeed::draw()
|
||||
{
|
||||
if (mStatusErrorText && mStatusLoadingText && mStatusLoadingIndicator)
|
||||
{
|
||||
mStatusErrorText->setVisible(false);
|
||||
mStatusLoadingText->setVisible(false);
|
||||
mStatusLoadingIndicator->setVisible(false);
|
||||
|
||||
FSPrimfeedConnect::EConnectionState connection_state = FSPrimfeedConnect::instance().getConnectionState();
|
||||
std::string status_text;
|
||||
|
||||
if (FSPrimfeedAuth::isAuthorized())
|
||||
{
|
||||
switch (connection_state)
|
||||
{
|
||||
case FSPrimfeedConnect::PRIMFEED_POSTING:
|
||||
{
|
||||
// Posting indicator
|
||||
mStatusLoadingText->setVisible(true);
|
||||
status_text = LLTrans::getString("SocialPrimfeedPosting");
|
||||
mStatusLoadingText->setValue(status_text);
|
||||
mStatusLoadingIndicator->setVisible(true);
|
||||
break;
|
||||
}
|
||||
case FSPrimfeedConnect::PRIMFEED_POST_FAILED:
|
||||
{
|
||||
// Error posting to the service
|
||||
mStatusErrorText->setVisible(true);
|
||||
status_text = LLTrans::getString("SocialPrimfeedErrorPosting");
|
||||
mStatusErrorText->setValue(status_text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (FSPrimfeedAuth::isPendingAuth())
|
||||
{
|
||||
// Show the status text when authorisation is pending
|
||||
mStatusLoadingText->setVisible(true);
|
||||
status_text = LLTrans::getString("SocialPrimfeedConnecting");
|
||||
mStatusLoadingText->setValue(status_text);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Show the status text when not authorised
|
||||
mStatusErrorText->setVisible(true);
|
||||
status_text = LLTrans::getString("SocialPrimfeedNotAuthorized");
|
||||
mStatusErrorText->setValue(status_text);
|
||||
}
|
||||
}
|
||||
LLFloater::draw();
|
||||
}
|
||||
|
||||
|
||||
void FSFloaterPrimfeed::onOpen(const LLSD& key)
|
||||
{
|
||||
mPrimfeedPhotoPanel->onOpen(key);
|
||||
}
|
||||
LLSnapshotLivePreview* FSFloaterPrimfeed::getPreviewView()
|
||||
{
|
||||
if(mPrimfeedPhotoPanel)
|
||||
{
|
||||
return mPrimfeedPhotoPanel->getPreviewView();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
/**
|
||||
* @file fsfloaterprimfeed.cpp
|
||||
* @brief Declaration of primfeed floater
|
||||
* @author beq@firestorm
|
||||
*
|
||||
* $LicenseInfo:firstyear=2025&license=fsviewerlgpl$
|
||||
* Phoenix Firestorm Viewer Source Code
|
||||
* Copyright (C) 2025, Beq Janus
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
|
||||
* http://www.firestormviewer.org
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
#ifndef FS_FLOATERPRIMFEED_H
|
||||
#define FS_FLOATERPRIMFEED_H
|
||||
|
||||
#include "llfloater.h"
|
||||
#include "lltextbox.h"
|
||||
#include "llviewertexture.h"
|
||||
|
||||
class LLIconCtrl;
|
||||
class LLCheckBoxCtrl;
|
||||
class LLSnapshotLivePreview;
|
||||
class LLFloaterBigPreview;
|
||||
|
||||
/*
|
||||
* (TODO) Beq: Refactor this with Flickr
|
||||
* Primfeed floater is copied heavily from the LLFlaoterFlickr class and deliberately implemetns much of the underlying plumbinng into the connector class.
|
||||
* Once this is bedded in and any initial issues are addressed, it would be sensible to refactor both the flickr and primfeed classes to share a common base.
|
||||
* In particular a ref counted test for the livepreview would eliminate the need for the static update method in the app mainloop.
|
||||
*/
|
||||
class FSPrimfeedPhotoPanel : public LLPanel
|
||||
{
|
||||
public:
|
||||
FSPrimfeedPhotoPanel();
|
||||
~FSPrimfeedPhotoPanel();
|
||||
|
||||
bool postBuild();
|
||||
S32 notify(const LLSD& info);
|
||||
void draw();
|
||||
|
||||
LLSnapshotLivePreview* getPreviewView();
|
||||
void onVisibilityChange(bool new_visibility);
|
||||
void onClickNewSnapshot();
|
||||
void onClickBigPreview();
|
||||
void onSend();
|
||||
bool onPrimfeedConnectStateChange(const LLSD& data);
|
||||
|
||||
void sendPhoto();
|
||||
void clearAndClose();
|
||||
|
||||
void updateControls();
|
||||
void updateResolution(bool do_update);
|
||||
void checkAspectRatio(S32 index);
|
||||
LLUICtrl* getRefreshBtn();
|
||||
|
||||
/*virtual*/ void onOpen(const LLSD& key);
|
||||
void primfeedAuthResponse(bool success, const LLSD& response);
|
||||
void uploadCallback(bool success, const LLSD& response);
|
||||
|
||||
private:
|
||||
bool isPreviewVisible();
|
||||
void attachPreview();
|
||||
|
||||
bool checkImageSize(LLSnapshotLivePreview* previewp, S32& width, S32& height, bool isWidthChanged, S32 max_value);
|
||||
|
||||
LLHandle<LLView> mPreviewHandle;
|
||||
|
||||
LLUICtrl * mResolutionComboBox;
|
||||
LLUICtrl * mFilterComboBox;
|
||||
LLUICtrl * mRefreshBtn;
|
||||
LLUICtrl * mWorkingLabel;
|
||||
LLUICtrl * mThumbnailPlaceholder;
|
||||
LLUICtrl * mDescriptionTextBox;
|
||||
LLUICtrl * mLocationCheckbox;
|
||||
|
||||
LLUICtrl * mCommercialCheckbox;
|
||||
LLUICtrl * mPublicGalleryCheckbox;
|
||||
LLUICtrl * mRatingComboBox;
|
||||
LLUICtrl * mPostButton;
|
||||
LLUICtrl * mCancelButton;
|
||||
LLButton * mBtnPreview;
|
||||
|
||||
LLFloaterBigPreview * mBigPreviewFloater;
|
||||
};
|
||||
|
||||
class FSPrimfeedAccountPanel : public LLPanel
|
||||
{
|
||||
public:
|
||||
FSPrimfeedAccountPanel();
|
||||
bool postBuild();
|
||||
void draw();
|
||||
|
||||
private:
|
||||
void onVisibilityChange(bool new_visibility);
|
||||
void primfeedAuthResponse(bool success, const LLSD& response);
|
||||
bool onPrimfeedConnectStateChange(const LLSD& data);
|
||||
bool onPrimfeedConnectInfoChange();
|
||||
void onConnect();
|
||||
void onUseAnotherAccount();
|
||||
void onDisconnect();
|
||||
|
||||
void showConnectButton();
|
||||
void hideConnectButton();
|
||||
void showDisconnectedLayout();
|
||||
void showConnectedLayout();
|
||||
|
||||
LLTextBox * mAccountConnectedAsLabel;
|
||||
LLTextBox * mAccountNameLink;
|
||||
LLTextBox * mAccountPlan;
|
||||
LLUICtrl * mPanelButtons;
|
||||
LLUICtrl * mConnectButton;
|
||||
LLUICtrl * mDisconnectButton;
|
||||
};
|
||||
|
||||
|
||||
class FSFloaterPrimfeed : public LLFloater
|
||||
{
|
||||
public:
|
||||
FSFloaterPrimfeed(const LLSD& key);
|
||||
static void update();
|
||||
bool postBuild();
|
||||
void draw();
|
||||
void onClose(bool app_quitting);
|
||||
void onCancel();
|
||||
|
||||
void showPhotoPanel();
|
||||
|
||||
void onOpen(const LLSD& key);
|
||||
LLSnapshotLivePreview* getPreviewView();
|
||||
|
||||
private:
|
||||
FSPrimfeedPhotoPanel* mPrimfeedPhotoPanel;
|
||||
FSPrimfeedAccountPanel* mPrimfeedAccountPanel;
|
||||
LLTextBox* mStatusErrorText;
|
||||
LLTextBox* mStatusLoadingText;
|
||||
LLUICtrl* mStatusLoadingIndicator;
|
||||
};
|
||||
|
||||
#endif // LL_FSFLOATERPRIMFEED_H
|
||||
|
||||
|
|
@ -0,0 +1,468 @@
|
|||
/**
|
||||
* @file fsprimfeedauth.cpp
|
||||
* @file fsprimfeedauth.h
|
||||
* @brief Primfeed Authorisation workflow class
|
||||
* @author beq@firestorm
|
||||
* $LicenseInfo:firstyear=2025&license=fsviewerlgpl$
|
||||
* Phoenix Firestorm Viewer Source Code
|
||||
* Copyright (C) 2025, Beq Janus
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
|
||||
* http://www.firestormviewer.org
|
||||
* $/LicenseInfo$
|
||||
*
|
||||
/*
|
||||
* Handles Primfeed authentication and authorisation through a multi-factor OAuth flow.
|
||||
*
|
||||
* This module integrates with Primfeed’s Third Party Viewers API.
|
||||
* The authentication flow is as follows:
|
||||
* 1. Initiate a login request:
|
||||
* POST https://api.primfeed.com/pf/viewer/create-login-request
|
||||
* Headers:
|
||||
* pf-viewer-api-key: <viewer_api_key>
|
||||
* pf-user-uuid: <avatar_uuid>
|
||||
* Response:
|
||||
* { "requestId": "<64-char string>" }
|
||||
*
|
||||
* 2. Redirect the user to:
|
||||
* https://www.primfeed.com/oauth/viewer?r=<requestId>&v=<viewer_api_key>
|
||||
*
|
||||
* 3. The user is shown an approval screen. When they click Authorize,
|
||||
* an in-world message is sent:
|
||||
* #PRIMFEED_OAUTH: <oauth_token>
|
||||
* We intercept this code through an onChat handle then call onOauthTokenReceived().
|
||||
*
|
||||
* 4. Validate the login request:
|
||||
* POST https://api.primfeed.com/pf/viewer/validate-request
|
||||
* Headers:
|
||||
* Authorization: Bearer <oauth_token>
|
||||
* pf-viewer-api-key: <viewer_api_key>
|
||||
* pf-viewer-request-id: <requestId>
|
||||
* Response: HTTP 204
|
||||
*
|
||||
* 5. Optionally, check user status:
|
||||
* GET https://api.primfeed.com/pf/viewer/user
|
||||
* Headers:
|
||||
* Authorization: Bearer <oauth_token>
|
||||
* pf-viewer-api-key: <viewer_api_key>
|
||||
* Response: { "plan": "free" } (or "pro")
|
||||
*/
|
||||
#include "llviewerprecompiledheaders.h"
|
||||
#include "fsprimfeedauth.h"
|
||||
#include "fsprimfeedconnect.h"
|
||||
#include "llimview.h"
|
||||
#include "llnotificationsutil.h"
|
||||
#include "llfloaterimnearbychathandler.h"
|
||||
#include "llnotificationmanager.h"
|
||||
#include "llagent.h"
|
||||
#include "llevents.h"
|
||||
#include "fscorehttputil.h"
|
||||
#include "llwindow.h"
|
||||
#include "llviewerwindow.h"
|
||||
#include "lluri.h"
|
||||
#include "llsdjson.h"
|
||||
#include <string_view>
|
||||
|
||||
using Callback = FSPrimfeedAuth::authorized_callback_t;
|
||||
|
||||
// private instance variable
|
||||
std::shared_ptr<FSPrimfeedAuth> FSPrimfeedAuth::sPrimfeedAuth;
|
||||
std::unique_ptr<LLEventPump> FSPrimfeedAuth::sPrimfeedAuthPump = std::make_unique<LLEventStream>("PrimfeedAuthResponse");
|
||||
|
||||
// Helper callback that unpacks HTTP POST response data.
|
||||
void FSPrimfeedAuthResponse(LLSD const &aData, Callback callback)
|
||||
{
|
||||
LLSD header = aData[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS][LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS];
|
||||
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(
|
||||
aData[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]);
|
||||
|
||||
const LLSD::Binary &rawData = aData[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary();
|
||||
std::string result;
|
||||
result.assign(rawData.begin(), rawData.end());
|
||||
|
||||
// Assume JSON response.
|
||||
|
||||
LLSD resultLLSD;
|
||||
if(!result.empty())
|
||||
{
|
||||
resultLLSD = LlsdFromJson(boost::json::parse(result));
|
||||
}
|
||||
callback((status.getType() == HTTP_OK ||
|
||||
status.getType() == HTTP_NO_CONTENT), resultLLSD);
|
||||
}
|
||||
|
||||
void FSPrimfeedAuth::initiateAuthRequest()
|
||||
{
|
||||
// This function is called to initiate the authentication request.
|
||||
// It should be called when the user clicks the "Authenticate" button.
|
||||
// Also triggered on opening the floater.
|
||||
// The actual implementation is in the create() method.
|
||||
|
||||
if (!isAuthorized())
|
||||
{
|
||||
if (sPrimfeedAuth)
|
||||
{
|
||||
LLNotificationsUtil::add("PrimfeedAuthorisationAlreadyInProgress");
|
||||
return;
|
||||
}
|
||||
// If no token stored, begin the login request; otherwise check user status.
|
||||
sPrimfeedAuth = FSPrimfeedAuth::create(
|
||||
[](bool success, const LLSD &response)
|
||||
{
|
||||
LLSD event_data = response;
|
||||
event_data["success"] = success;
|
||||
sPrimfeedAuthPump->post(event_data);
|
||||
// Now that auth is complete, clear the static pointer.
|
||||
sPrimfeedAuth.reset();
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
LLNotificationsUtil::add("PrimfeedAlreadyAuthorized");
|
||||
}
|
||||
}
|
||||
|
||||
void FSPrimfeedAuth::resetAuthStatus()
|
||||
{
|
||||
sPrimfeedAuth.reset();
|
||||
gSavedPerAccountSettings.setString("FSPrimfeedOAuthToken", "");
|
||||
gSavedPerAccountSettings.setString("FSPrimfeedProfileLink", "");
|
||||
gSavedPerAccountSettings.setString("FSPrimfeedPlan", "");
|
||||
gSavedPerAccountSettings.setString("FSPrimfeedUsername", "");
|
||||
LLSD event_data;
|
||||
event_data["status"] = "reset";
|
||||
event_data["success"] = "false";
|
||||
sPrimfeedAuthPump->post(event_data);
|
||||
}
|
||||
|
||||
|
||||
FSPrimfeedAuth::FSPrimfeedAuth(authorized_callback_t callback)
|
||||
: mCallback(callback), mAuthenticating(false)
|
||||
{
|
||||
mInstantMessageConnection = LLIMModel::instance().addNewMsgCallback(
|
||||
[this](const LLSD &message) {
|
||||
LL_DEBUGS("FSPrimfeedAuth") << "Received chat message: " << message["message"].asString() << LL_ENDL;
|
||||
this->onChatMessage(message);
|
||||
});
|
||||
mChatMessageConnection = LLNotificationsUI::LLNotificationManager::instance().getChatHandler()->addNewChatCallback(
|
||||
[this](const LLSD &message) {
|
||||
LL_DEBUGS("FSPrimfeedAuth") << "Received instant message: " << message["message"].asString() << LL_ENDL;
|
||||
this->onChatMessage(message);
|
||||
});
|
||||
}
|
||||
|
||||
FSPrimfeedAuth::~FSPrimfeedAuth()
|
||||
{
|
||||
if (mChatMessageConnection.connected())
|
||||
{
|
||||
try
|
||||
{
|
||||
mChatMessageConnection.disconnect();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
LL_WARNS("FSPrimfeedAuth") << "Exception during chat connection disconnect: " << e.what() << LL_ENDL;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LL_WARNS("FSPrimfeedAuth") << "Unknown exception during chat connection disconnect." << LL_ENDL;
|
||||
}
|
||||
}
|
||||
if (mInstantMessageConnection.connected())
|
||||
{
|
||||
try
|
||||
{
|
||||
mInstantMessageConnection.disconnect();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
LL_WARNS("FSPrimfeedAuth") << "Exception during instant message disconnect: " << e.what() << LL_ENDL;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LL_WARNS("FSPrimfeedAuth") << "Unknown exception during instant message disconnect." << LL_ENDL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Factory method to create a shared pointer to FSPrimfeedAuth.
|
||||
std::shared_ptr<FSPrimfeedAuth> FSPrimfeedAuth::create(authorized_callback_t callback)
|
||||
{
|
||||
// Ensure only one authentication attempt is in progress.
|
||||
if (sPrimfeedAuth)
|
||||
{
|
||||
// Already in progress; return the existing instance.
|
||||
return sPrimfeedAuth;
|
||||
}
|
||||
auto auth = std::shared_ptr<FSPrimfeedAuth>(new FSPrimfeedAuth(callback));
|
||||
if(!auth)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auth->mAuthenticating = true;
|
||||
|
||||
// If no token stored, begin the login request; otherwise check user status.
|
||||
if (gSavedPerAccountSettings.getString("FSPrimfeedOAuthToken").empty())
|
||||
{
|
||||
auth->beginLoginRequest();
|
||||
}
|
||||
else
|
||||
{
|
||||
auth->checkUserStatus();
|
||||
}
|
||||
return auth;
|
||||
}
|
||||
|
||||
void FSPrimfeedAuth::beginLoginRequest()
|
||||
{
|
||||
// Get our API key and user UUID.
|
||||
std::string viewer_api_key = gSavedSettings.getString("FSPrimfeedViewerApiKey");
|
||||
std::string user_uuid = gAgent.getID().asString();
|
||||
|
||||
std::string url = "https://api.primfeed.com/pf/viewer/create-login-request";
|
||||
std::string post_data = ""; // No body parameters required.
|
||||
|
||||
// Create the headers object.
|
||||
LLCore::HttpHeaders::ptr_t pHeader(new LLCore::HttpHeaders());
|
||||
LLCore::HttpOptions::ptr_t options(new LLCore::HttpOptions());
|
||||
|
||||
pHeader->append("pf-viewer-api-key", viewer_api_key);
|
||||
pHeader->append("pf-user-uuid", user_uuid);
|
||||
|
||||
// Set up HTTP options
|
||||
options->setWantHeaders(true);
|
||||
options->setRetries(0);
|
||||
options->setTimeout(PRIMFEED_CONNECT_TIMEOUT);
|
||||
|
||||
// Capture shared_ptr to self
|
||||
auto self = shared_from_this();
|
||||
|
||||
const auto end(pHeader->end());
|
||||
for (auto it(pHeader->begin()); end != it; ++it)
|
||||
{
|
||||
LL_DEBUGS("Primfeed") << "Header: " << it->first << " = " << it->second << LL_ENDL;
|
||||
}
|
||||
|
||||
// Pass both success and failure callbacks
|
||||
FSCoreHttpUtil::callbackHttpPostRaw(
|
||||
url,
|
||||
post_data,
|
||||
[self](LLSD const &aData) {
|
||||
LL_DEBUGS("FSPrimfeedAuth") << "Login request response(OK): " << aData << LL_ENDL;
|
||||
FSPrimfeedAuthResponse(aData,
|
||||
[self](bool success, const LLSD &response) {
|
||||
self->gotRequestId(success, response);
|
||||
}
|
||||
);
|
||||
},
|
||||
[self](LLSD const &aData) {
|
||||
LL_DEBUGS("FSPrimfeedAuth") << "Login request response(FAIL): " << aData << LL_ENDL;
|
||||
FSPrimfeedAuthResponse(aData,
|
||||
[self](bool success, const LLSD &response) {
|
||||
self->gotRequestId(success, response);
|
||||
}
|
||||
);
|
||||
},
|
||||
pHeader,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
void FSPrimfeedAuth::gotRequestId(bool success, const LLSD &response)
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
LLNotificationsUtil::add("PrimfeedLoginRequestFailed");
|
||||
mCallback(false, LLSD());
|
||||
return;
|
||||
}
|
||||
mRequestId = response["requestId"].asString();
|
||||
if (mRequestId.empty())
|
||||
{
|
||||
LLNotificationsUtil::add("PrimfeedLoginRequestFailed");
|
||||
mCallback(false, LLSD());
|
||||
return;
|
||||
}
|
||||
// Open the browser for user approval.
|
||||
std::string viewer_api_key = gSavedSettings.getString("FSPrimfeedViewerApiKey");
|
||||
std::string auth_url = "https://www.primfeed.com/oauth/viewer?r=" + mRequestId + "&v=" + viewer_api_key;
|
||||
gViewerWindow->getWindow()->spawnWebBrowser(auth_url, true);
|
||||
|
||||
}
|
||||
|
||||
/// This function is called by the chat interceptor when the message
|
||||
/// "#PRIMFEED_OAUTH: <oauth_token>" is intercepted.
|
||||
void FSPrimfeedAuth::onOauthTokenReceived(const std::string_view& oauth_token)
|
||||
{
|
||||
if (oauth_token.empty())
|
||||
{
|
||||
mCallback(false, LLSD());
|
||||
return;
|
||||
}
|
||||
mOauthToken = oauth_token;
|
||||
validateRequest();
|
||||
}
|
||||
|
||||
void FSPrimfeedAuth::onChatMessage(const LLSD& message)
|
||||
{
|
||||
constexpr std::string_view oauth_msg_prefix = "#PRIMFEED_OAUTH: ";
|
||||
const std::string msg = message["message"].asString();
|
||||
if (msg.find(std::string(oauth_msg_prefix)) == 0)
|
||||
{
|
||||
std::string_view oauth_token(msg.data() + oauth_msg_prefix.size(), msg.size() - oauth_msg_prefix.size());
|
||||
LL_DEBUGS("Primfeed") << "Received OAuth token: " << msg << "extracted:<" << oauth_token << ">" << LL_ENDL;
|
||||
onOauthTokenReceived(oauth_token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FSPrimfeedAuth::validateRequest()
|
||||
{
|
||||
// No POST body needed.
|
||||
std::string post_data = "";
|
||||
std::string url = "https://api.primfeed.com/pf/viewer/validate-request";
|
||||
|
||||
// Retrieve the viewer API key.
|
||||
std::string viewer_api_key = gSavedSettings.getString("FSPrimfeedViewerApiKey");
|
||||
|
||||
// Create and populate the headers.
|
||||
LLCore::HttpHeaders::ptr_t pHeader(new LLCore::HttpHeaders());
|
||||
pHeader->append("Authorization", "Bearer " + mOauthToken);
|
||||
pHeader->append("pf-viewer-api-key", viewer_api_key);
|
||||
pHeader->append("pf-viewer-request-id", mRequestId);
|
||||
|
||||
// Set HTTP options
|
||||
LLCore::HttpOptions::ptr_t options(new LLCore::HttpOptions());
|
||||
options->setWantHeaders(true);
|
||||
options->setRetries(0);
|
||||
options->setTimeout(PRIMFEED_CONNECT_TIMEOUT);
|
||||
|
||||
// print out pHeader for debuging using iterating over pHeader and using LL_DEBUGS
|
||||
const auto end(pHeader->end());
|
||||
for (auto it(pHeader->begin()); end != it; ++it)
|
||||
{
|
||||
LL_DEBUGS("Primfeed") << "Header: " << it->first << " = " << it->second << LL_ENDL;
|
||||
}
|
||||
|
||||
auto self = shared_from_this();
|
||||
try
|
||||
{
|
||||
FSCoreHttpUtil::callbackHttpPostRaw(
|
||||
url,
|
||||
post_data,
|
||||
[self](LLSD const &aData) {
|
||||
LL_DEBUGS("FSPrimfeedAuth") << "Validation-request response(OK): " << aData << LL_ENDL;
|
||||
FSPrimfeedAuthResponse(aData,
|
||||
[self](bool success, const LLSD &response) {
|
||||
self->gotValidateResponse(success, response);
|
||||
}
|
||||
);
|
||||
},
|
||||
[self](LLSD const &aData) {
|
||||
LL_INFOS("FSPrimfeedAuth") << "Validation-request response(FAIL): " << aData << LL_ENDL;
|
||||
FSPrimfeedAuthResponse(aData,
|
||||
[self](bool success, const LLSD &response) {
|
||||
self->gotValidateResponse(success, response);
|
||||
}
|
||||
);
|
||||
},
|
||||
pHeader,
|
||||
options
|
||||
);
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
LL_WARNS("Primfeed") << "Primfeed validation failed " << e.what() << LL_ENDL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void FSPrimfeedAuth::gotValidateResponse(bool success, const LLSD &response)
|
||||
{
|
||||
if (!success)
|
||||
{
|
||||
LLNotificationsUtil::add("PrimfeedValidateFailed");
|
||||
mCallback(false, response);
|
||||
return;
|
||||
}
|
||||
checkUserStatus();
|
||||
}
|
||||
|
||||
void FSPrimfeedAuth::checkUserStatus()
|
||||
{
|
||||
std::string viewer_api_key = gSavedSettings.getString("FSPrimfeedViewerApiKey");
|
||||
|
||||
// Build the base URL without query parameters.
|
||||
std::string url = "https://api.primfeed.com/pf/viewer/user";
|
||||
LL_DEBUGS("Primfeed") << "URL: " << url << LL_ENDL;
|
||||
|
||||
// Create and populate the headers.
|
||||
LLCore::HttpHeaders::ptr_t pHeader(new LLCore::HttpHeaders());
|
||||
pHeader->append("Authorization", "Bearer " + mOauthToken);
|
||||
pHeader->append("pf-viewer-api-key", viewer_api_key);
|
||||
|
||||
// Set HTTP options.
|
||||
LLCore::HttpOptions::ptr_t options(new LLCore::HttpOptions());
|
||||
options->setWantHeaders(true);
|
||||
options->setRetries(0);
|
||||
options->setTimeout(PRIMFEED_CONNECT_TIMEOUT);
|
||||
|
||||
// Make the HTTP GET request, passing in the headers and options.
|
||||
FSCoreHttpUtil::callbackHttpGetRaw(
|
||||
url,
|
||||
[this](LLSD const &aData) {
|
||||
LL_DEBUGS("FSPrimfeedAuth") << "Check-user-status response: " << aData << LL_ENDL;
|
||||
FSPrimfeedAuthResponse(aData, [this](bool success, const LLSD &response) {
|
||||
this->gotUserStatus(success, response);
|
||||
});
|
||||
},
|
||||
[this](LLSD const &aData) {
|
||||
LL_INFOS("FSPrimfeedAuth") << "Check-user-status response (failure): " << aData << LL_ENDL;
|
||||
// Optionally, call the same processing for failure or handle separately.
|
||||
FSPrimfeedAuthResponse(aData, [this](bool success, const LLSD &response){
|
||||
this->gotUserStatus(success, response);
|
||||
});
|
||||
},
|
||||
pHeader,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void FSPrimfeedAuth::gotUserStatus(bool success, const LLSD &response)
|
||||
{
|
||||
LL_INFOS("Primfeed") << "User status: " << response << "(" << success << ")" << LL_ENDL;
|
||||
if (success && response.has("plan"))
|
||||
{
|
||||
gSavedPerAccountSettings.setString("FSPrimfeedOAuthToken", mOauthToken);
|
||||
gSavedPerAccountSettings.setString("FSPrimfeedRequestId", mRequestId);
|
||||
gSavedPerAccountSettings.setString("FSPrimfeedPlan", response["plan"].asString());
|
||||
gSavedPerAccountSettings.setString("FSPrimfeedProfileLink", response["link"].asString());
|
||||
gSavedPerAccountSettings.setString("FSPrimfeedUsername", response["username"].asString());
|
||||
FSPrimfeedConnect::instance().setConnectionState(FSPrimfeedConnect::PRIMFEED_CONNECTED);
|
||||
mCallback(true, response);
|
||||
}
|
||||
else
|
||||
{
|
||||
LLNotificationsUtil::add("PrimfeedUserStatusFailed");
|
||||
FSPrimfeedConnect::instance().setConnectionState(FSPrimfeedConnect::PRIMFEED_DISCONNECTED);
|
||||
mCallback(false, response);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* @file fsprimfeedauth.h
|
||||
* @brief Primfeed Authorisation workflow class
|
||||
* @author beq@firestorm
|
||||
*
|
||||
* $LicenseInfo:firstyear=2025&license=fsviewerlgpl$
|
||||
* Phoenix Firestorm Viewer Source Code
|
||||
* Copyright (C) 2025, Beq Janus
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
|
||||
* http://www.firestormviewer.org
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
#ifndef FSPRIMFEEDAUTH_H
|
||||
#define FSPRIMFEEDAUTH_H
|
||||
|
||||
#include "llsd.h"
|
||||
#include "llviewercontrol.h"
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
/*
|
||||
* Primfeed authentication workflow class.
|
||||
*
|
||||
* This class handles the Primfeed OAuth login flow and provides methods to
|
||||
* check the user status and receive a callback when the authentication
|
||||
* process is complete.
|
||||
* based on the workflow documented at https://docs.primfeed.com/api/third-party-viewers
|
||||
*/
|
||||
class FSPrimfeedAuth : public std::enable_shared_from_this<FSPrimfeedAuth>
|
||||
{
|
||||
public:
|
||||
// Callback type: first parameter indicates success and the second holds any LLSD response.
|
||||
using authorized_callback_t = std::function<void(bool, const LLSD&)>;
|
||||
static std::shared_ptr<FSPrimfeedAuth> create(authorized_callback_t callback);
|
||||
static std::unique_ptr<LLEventPump> sPrimfeedAuthPump;
|
||||
~FSPrimfeedAuth();
|
||||
|
||||
// Should be called by the chat interceptor when an oauth token is received.
|
||||
void onOauthTokenReceived(const std::string_view& oauth_token);
|
||||
void onInstantMessage(const LLSD& message);
|
||||
void onChatMessage(const LLSD& message);
|
||||
|
||||
// Begin the login request flow.
|
||||
void beginLoginRequest();
|
||||
// Check the user status.
|
||||
void checkUserStatus();
|
||||
static bool isPendingAuth(){ return (sPrimfeedAuth != nullptr); }
|
||||
static bool isAuthorized(){ return (!gSavedPerAccountSettings.getString("FSPrimfeedOAuthToken").empty()); }
|
||||
static void initiateAuthRequest();
|
||||
static void resetAuthStatus();
|
||||
|
||||
private:
|
||||
static std::shared_ptr<FSPrimfeedAuth> sPrimfeedAuth;
|
||||
|
||||
explicit FSPrimfeedAuth(authorized_callback_t callback);
|
||||
authorized_callback_t mCallback;
|
||||
bool mAuthenticating;
|
||||
std::string mOauthToken;
|
||||
std::string mRequestId;
|
||||
|
||||
// Callback when a login request response is received.
|
||||
void gotRequestId(bool success, const LLSD &response);
|
||||
// Validate the login request.
|
||||
void validateRequest();
|
||||
// Callback when the validate response is received.
|
||||
void gotValidateResponse(bool success, const LLSD &response);
|
||||
// Callback when the user status response is received.
|
||||
void gotUserStatus(bool success, const LLSD &response);
|
||||
|
||||
boost::signals2::connection mInstantMessageConnection;
|
||||
boost::signals2::connection mChatMessageConnection;
|
||||
// Static flag to prevent duplicate authentication attempts.
|
||||
static std::atomic<bool> sAuthorisationInProgress;
|
||||
|
||||
static constexpr U32 PRIMFEED_CONNECT_TIMEOUT = 300; // 5 minute timeout should work
|
||||
};
|
||||
|
||||
#endif // FSPRIMFEEDAUTH_H
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
/**
|
||||
* @file fsprimfeedconnect.cpp
|
||||
* @brief Primfeed connector class
|
||||
* @author beq@firestorm
|
||||
*
|
||||
* $LicenseInfo:firstyear=2025&license=fsviewerlgpl$
|
||||
* Phoenix Firestorm Viewer Source Code
|
||||
* Copyright (C) 2025, Beq Janus
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
|
||||
* http://www.firestormviewer.org
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
#include "fsprimfeedconnect.h"
|
||||
#include "fsprimfeedauth.h"
|
||||
#include "llviewercontrol.h"
|
||||
#include "llcoros.h"
|
||||
#include "llsdjson.h"
|
||||
|
||||
// The connector workflow for Primfeed is realtively simple and mostly just builds on top of the established Auth workflow
|
||||
// and the posting endpoint documented at https://docs.primfeed.com/api/third-party-viewers#creating-a-post
|
||||
|
||||
FSPrimfeedConnect::FSPrimfeedConnect() = default;
|
||||
|
||||
void FSPrimfeedConnect::uploadPhoto(const LLSD& params, LLImageFormatted* image, post_callback_t callback)
|
||||
{
|
||||
LL_DEBUGS("primfeed") << "uploadPhoto() called" << LL_ENDL;
|
||||
if (!FSPrimfeedAuth::isAuthorized())
|
||||
{
|
||||
LL_WARNS("primfeed") << "Authorization failed, aborting.\n" << LL_ENDL;
|
||||
callback(false, "");
|
||||
return;
|
||||
}
|
||||
LL_DEBUGS("primfeed") << "Authorization successful" << LL_ENDL;
|
||||
|
||||
mPostCallback = callback;
|
||||
LL_DEBUGS("primfeed") << "Launching upload coroutine" << LL_ENDL;
|
||||
LLCoros::instance().launch(
|
||||
"FSPrimfeedConnect::uploadPhotoCoro",
|
||||
[this, params, image]() { uploadPhotoCoro(params, image); }
|
||||
);
|
||||
}
|
||||
|
||||
void FSPrimfeedConnect::uploadPhotoCoro(const LLSD& params, LLImageFormatted* image)
|
||||
{
|
||||
LL_DEBUGS("primfeed") << "Entered uploadPhotoCoro" << LL_ENDL;
|
||||
setConnectionState(PRIMFEED_POSTING);
|
||||
LL_DEBUGS("primfeed") << "Connection state set to PRIMFEED_POSTING" << LL_ENDL;
|
||||
|
||||
const std::string fmt = (image->getCodec() == EImageCodec::IMG_CODEC_JPEG) ? "jpg" : "png";
|
||||
LL_DEBUGS("primfeed") << "Image format: " << fmt << LL_ENDL;
|
||||
|
||||
const std::string boundary = "----------------------------0123456789abcdef";
|
||||
const std::string sep = "\n";
|
||||
const std::string dash = "--" + boundary;
|
||||
|
||||
LL_DEBUGS("primfeed") << "Building multipart body" << LL_ENDL;
|
||||
LLCore::BufferArray::ptr_t raw(new LLCore::BufferArray());
|
||||
LLCore::BufferArrayStream body(raw.get());
|
||||
auto addPart = [&](const std::string& name, const std::string& val)
|
||||
{
|
||||
LL_DEBUGS("primfeed") << "Adding part: " << name << "=" << val << LL_ENDL;
|
||||
body << dash << sep
|
||||
<< "Content-Disposition: form-data; name=\"" << name << "\"" << sep << sep
|
||||
<< val << sep;
|
||||
};
|
||||
|
||||
addPart("commercial", params["commercial"].asBoolean() ? "true" : "false");
|
||||
addPart("rating", params["rating"].asString());
|
||||
addPart("content", params["content"].asString());
|
||||
addPart("publicGallery", params["post_to_public_gallery"].asBoolean()? "true" : "false");
|
||||
|
||||
if (params.has("location") && !params["location"].asString().empty())
|
||||
{
|
||||
addPart("location", params["location"].asString());
|
||||
}
|
||||
|
||||
LL_DEBUGS("primfeed") << "Adding image file header" << LL_ENDL;
|
||||
body << dash << sep
|
||||
<< "Content-Disposition: form-data; name=\"image\"; filename=\"snapshot." << fmt << "\"" << sep
|
||||
<< "Content-Type: image/" << fmt << sep << sep;
|
||||
|
||||
U8* data = image->getData();
|
||||
S32 size = image->getDataSize();
|
||||
LL_DEBUGS("primfeed") << "Appending image data, size=" << size << LL_ENDL;
|
||||
// yep this seems inefficient, but all other occurrences in the codebase do it this way.
|
||||
for (S32 i = 0; i < size; ++i)
|
||||
{
|
||||
body << data[i];
|
||||
}
|
||||
body << sep;
|
||||
|
||||
body << dash << "--" << sep;
|
||||
LL_DEBUGS("primfeed") << "Multipart body ready" << LL_ENDL;
|
||||
|
||||
// Setup HTTP
|
||||
LL_DEBUGS("primfeed") << "Preparing HTTP request" << LL_ENDL;
|
||||
LLCore::HttpRequest::policy_t policy = LLCore::HttpRequest::DEFAULT_POLICY_ID;
|
||||
LLCoreHttpUtil::HttpCoroutineAdapter adapter("PrimfeedUpload", policy);
|
||||
LLCore::HttpRequest::ptr_t request(new LLCore::HttpRequest);
|
||||
LLCore::HttpOptions::ptr_t options(new LLCore::HttpOptions);
|
||||
options->setWantHeaders(true);
|
||||
|
||||
LL_DEBUGS("primfeed") << "Setting HTTP headers" << LL_ENDL;
|
||||
LLCore::HttpHeaders::ptr_t headers(new LLCore::HttpHeaders);
|
||||
std::string token = gSavedPerAccountSettings.getString("FSPrimfeedOAuthToken");
|
||||
std::string apiKey = gSavedSettings.getString("FSPrimfeedViewerApiKey");
|
||||
headers->append("Authorization", "Bearer " + token);
|
||||
headers->append("pf-viewer-api-key", apiKey);
|
||||
headers->append("Content-Type", "multipart/form-data; boundary=" + boundary);
|
||||
LL_DEBUGS("primfeed") << "Dumping HTTP headers for POST:" << LL_ENDL;
|
||||
for (auto it = headers->begin(); it != headers->end(); ++it)
|
||||
{
|
||||
LL_DEBUGS("primfeed") << it->first << ": " << it->second << LL_ENDL;
|
||||
}
|
||||
LL_DEBUGS("primfeed") << "Headers set" << LL_ENDL;
|
||||
|
||||
LL_DEBUGS("primfeed") << "Starting HTTP POST" << LL_ENDL;
|
||||
LLSD result = adapter.postRawAndSuspend(request,
|
||||
"https://api.primfeed.com/pf/viewer/post",
|
||||
raw,
|
||||
options,
|
||||
headers);
|
||||
LL_DEBUGS("primfeed") << "HTTP POST complete" << LL_ENDL;
|
||||
|
||||
const LLSD::Binary &rawData = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary();
|
||||
std::string response_raw;
|
||||
response_raw.assign(rawData.begin(), rawData.end());
|
||||
LLSD result_LLSD;
|
||||
if(!response_raw.empty())
|
||||
{
|
||||
result_LLSD = LlsdFromJson(boost::json::parse(response_raw));
|
||||
}
|
||||
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]);
|
||||
bool success = (status.getType() == HTTP_OK);
|
||||
LL_DEBUGS("primfeed") << "HTTP status =" << (success?"OK":"FAIL") << " "<< status.getMessage() << LL_ENDL;
|
||||
|
||||
std::string url;
|
||||
if (success)
|
||||
{
|
||||
url = result_LLSD["url"].asString();
|
||||
LL_DEBUGS("primfeed") << "Received URL=" << url << LL_ENDL;
|
||||
}
|
||||
|
||||
LL_DEBUGS("primfeed") << "Invoking callback" << LL_ENDL;
|
||||
mPostCallback(success, url);
|
||||
setConnectionState(success ? PRIMFEED_POSTED : PRIMFEED_POST_FAILED);
|
||||
LL_DEBUGS("primfeed") << "Final state set" << LL_ENDL;
|
||||
}
|
||||
|
||||
// Handle connection state transitions
|
||||
void FSPrimfeedConnect::setConnectionState(EConnectionState state)
|
||||
{
|
||||
LL_DEBUGS("primfeed") << "setConnectionState(" << state << ")" << LL_ENDL;
|
||||
mConnectionState = state;
|
||||
}
|
||||
|
||||
FSPrimfeedConnect::EConnectionState FSPrimfeedConnect::getConnectionState() const
|
||||
{
|
||||
return mConnectionState;
|
||||
}
|
||||
|
||||
bool FSPrimfeedConnect::isTransactionOngoing() const
|
||||
{
|
||||
return (mConnectionState == PRIMFEED_CONNECTING ||
|
||||
mConnectionState == PRIMFEED_POSTING ||
|
||||
mConnectionState == PRIMFEED_DISCONNECTING);
|
||||
}
|
||||
|
||||
void FSPrimfeedConnect::loadPrimfeedInfo()
|
||||
{
|
||||
LL_DEBUGS("primfeed") << "loadPrimfeedInfo() called" << LL_ENDL;
|
||||
// Nothing to do here for Primfeed
|
||||
setConnectionState(PRIMFEED_CONNECTED);
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* @file fsprimfeedconect.h
|
||||
* @brief Primfeed connector class
|
||||
* @author beq@firestorm
|
||||
*
|
||||
* $LicenseInfo:firstyear=2025&license=fsviewerlgpl$
|
||||
* Phoenix Firestorm Viewer Source Code
|
||||
* Copyright (C) 2025, Beq Janus
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA
|
||||
* http://www.firestormviewer.org
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
#ifndef FS_PRIMFEEDCONNECT_H
|
||||
#define FS_PRIMFEEDCONNECT_H
|
||||
|
||||
#include "llsingleton.h"
|
||||
#include "llsd.h"
|
||||
#include "llimage.h"
|
||||
#include "fsprimfeedauth.h"
|
||||
#include "llcorehttputil.h"
|
||||
#include "bufferarray.h"
|
||||
#include "llcoros.h"
|
||||
#include "llviewercontrol.h" // for gSavedSettings/gSavedPerAccountSettings
|
||||
#include <functional>
|
||||
|
||||
// Coro based connector designed to interface with floater designed along the same principles as LLFloaterFlickr.cpp
|
||||
|
||||
class FSPrimfeedConnect : public LLSingleton<FSPrimfeedConnect>
|
||||
{
|
||||
LLSINGLETON(FSPrimfeedConnect);
|
||||
public:
|
||||
// Connection states for Primfeed operations
|
||||
enum EConnectionState
|
||||
{
|
||||
PRIMFEED_DISCONNECTED = 0,
|
||||
PRIMFEED_CONNECTING,
|
||||
PRIMFEED_CONNECTED,
|
||||
PRIMFEED_POSTING,
|
||||
PRIMFEED_POSTED,
|
||||
PRIMFEED_POST_FAILED,
|
||||
PRIMFEED_DISCONNECTING
|
||||
};
|
||||
|
||||
// Callback invoked on post completion: success flag and URL (empty on failure)
|
||||
using post_callback_t = std::function<void(bool success, const std::string& url)>;
|
||||
|
||||
// Posts a snapshot to Primfeed; requires FSPrimfeedAuth::isAuthorized()
|
||||
void uploadPhoto(const LLSD& params, LLImageFormatted* image, post_callback_t callback);
|
||||
|
||||
// Retrieve and update account info from Primfeed (not used kept for compatibility)
|
||||
void loadPrimfeedInfo();
|
||||
|
||||
void setConnectionState(EConnectionState state);
|
||||
EConnectionState getConnectionState() const;
|
||||
bool isTransactionOngoing() const;
|
||||
|
||||
private:
|
||||
// Internal coroutine entry-point for uploads
|
||||
void uploadPhotoCoro(const LLSD& params, LLImageFormatted* image);
|
||||
|
||||
// Cached callback until coroutine completes
|
||||
post_callback_t mPostCallback;
|
||||
|
||||
// Current connection/post state
|
||||
EConnectionState mConnectionState = PRIMFEED_DISCONNECTED;
|
||||
};
|
||||
#endif // FS_PRIMFEEDCONNECT_H
|
||||
|
|
@ -235,6 +235,7 @@
|
|||
#include "llfloatersimplesnapshot.h"
|
||||
#include "llfloatersnapshot.h"
|
||||
#include "llfloaterflickr.h"
|
||||
#include "fsfloaterprimfeed.h" // <FS:Beq/> Primfeed Floater
|
||||
#include "llsidepanelinventory.h"
|
||||
#include "llatmosphere.h"
|
||||
|
||||
|
|
@ -1751,6 +1752,7 @@ bool LLAppViewer::doFrame()
|
|||
LLFloaterSnapshot::update(); // take snapshots
|
||||
LLFloaterSimpleSnapshot::update();
|
||||
LLFloaterFlickr::update(); // <FS:Beq/> FIRE-35002 - Flickr preview not updating whne opened directly from tool tray icon
|
||||
FSFloaterPrimfeed::update(); // <FS:Beq/> Primfeed support
|
||||
gGLActive = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
|
||||
#include "llfloaterreg.h"
|
||||
#include "llfloaterflickr.h" // <FS:Ansariel> Share to Flickr
|
||||
#include "fsfloaterprimfeed.h" // <FS:Beq> Share to Primfeed
|
||||
#include "llimagefiltersmanager.h"
|
||||
#include "llcheckboxctrl.h"
|
||||
#include "llcombobox.h"
|
||||
|
|
@ -1485,12 +1486,12 @@ bool LLFloaterSnapshot::isWaitingState()
|
|||
|
||||
// <FS:Beq> FIRE-35002 - Post to flickr broken, improved solution
|
||||
// bool LLFloaterSnapshotBase::ImplBase::updatePreviewList(bool initialized)
|
||||
bool LLFloaterSnapshotBase::ImplBase::updatePreviewList(bool initialized, bool have_flickr)
|
||||
bool LLFloaterSnapshotBase::ImplBase::updatePreviewList(bool initialized, bool have_socials)
|
||||
// </FS:Beq>
|
||||
{
|
||||
// <FS:Ansariel> Share to Flickr
|
||||
//if (!initialized)
|
||||
if (!initialized && !have_flickr)
|
||||
if (!initialized && !have_socials)
|
||||
// </FS:Ansariel>
|
||||
return false;
|
||||
|
||||
|
|
@ -1509,16 +1510,18 @@ void LLFloaterSnapshotBase::ImplBase::updateLivePreview()
|
|||
{
|
||||
// don't update preview for hidden floater
|
||||
// <FS:Beq> FIRE-35002 - Post to flickr broken
|
||||
LLFloaterFlickr* floater_flickr = LLFloaterReg::findTypedInstance<LLFloaterFlickr>("flickr");
|
||||
auto have_flickr = floater_flickr != nullptr;
|
||||
bool have_socials = (
|
||||
LLFloaterReg::findTypedInstance<LLFloaterFlickr>("flickr") != nullptr ||
|
||||
LLFloaterReg::findTypedInstance<FSFloaterPrimfeed>("primfeed") != nullptr
|
||||
);
|
||||
if ( ((mFloater && mFloater->isInVisibleChain()) ||
|
||||
have_flickr) &&
|
||||
ImplBase::updatePreviewList(true, have_flickr))
|
||||
have_socials) &&
|
||||
ImplBase::updatePreviewList(true, have_socials))
|
||||
// </FS:Beq>
|
||||
{
|
||||
LL_DEBUGS() << "changed" << LL_ENDL;
|
||||
updateControls(mFloater);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//static
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ public:
|
|||
virtual EStatus getStatus() const { return mStatus; }
|
||||
virtual void setNeedRefresh(bool need);
|
||||
|
||||
static bool updatePreviewList(bool initialized, bool have_flickr = false); // <FS:Beq/> FIRE-35002 - Post to flickr broken, improved solution
|
||||
static bool updatePreviewList(bool initialized, bool have_socials = false); // <FS:Beq/> FIRE-35002 - Post to flickr broken, improved solution
|
||||
|
||||
void setAdvanced(bool advanced) { mAdvanced = advanced; }
|
||||
void setSkipReshaping(bool skip) { mSkipReshaping = skip; }
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include "llfloatersnapshot.h" // FIXME: create a snapshot model
|
||||
#include "llfloaterreg.h"
|
||||
#include "llfloaterflickr.h" // <FS:Ansariel> Share to Flickr
|
||||
#include "fsfloaterprimfeed.h" // <FS:Beq> Share to Primfeed
|
||||
|
||||
/**
|
||||
* Provides several ways to save a snapshot.
|
||||
|
|
@ -53,6 +54,7 @@ private:
|
|||
void onSaveToInventory();
|
||||
void onSaveToComputer();
|
||||
void onSendToFlickr(); // <FS:Ansariel> Share to Flickr
|
||||
void onSendToPrimfeed(); // <FS:Beq/> Share to Primfeed
|
||||
|
||||
LLFloaterSnapshotBase* mSnapshotFloater;
|
||||
};
|
||||
|
|
@ -66,6 +68,7 @@ LLPanelSnapshotOptions::LLPanelSnapshotOptions()
|
|||
mCommitCallbackRegistrar.add("Snapshot.SaveToInventory", boost::bind(&LLPanelSnapshotOptions::onSaveToInventory, this));
|
||||
mCommitCallbackRegistrar.add("Snapshot.SaveToComputer", boost::bind(&LLPanelSnapshotOptions::onSaveToComputer, this));
|
||||
mCommitCallbackRegistrar.add("Snapshot.SendToFlickr", boost::bind(&LLPanelSnapshotOptions::onSendToFlickr, this)); // <FS:Ansariel> Share to Flickr
|
||||
mCommitCallbackRegistrar.add("Snapshot.SendToPrimfeed", boost::bind(&LLPanelSnapshotOptions::onSendToPrimfeed, this)); // <FS:Beq/> Share to Primfeed
|
||||
}
|
||||
|
||||
// virtual
|
||||
|
|
@ -122,3 +125,17 @@ void LLPanelSnapshotOptions::onSendToFlickr()
|
|||
LLFloaterReg::showInstance("flickr");
|
||||
}
|
||||
// </FS:Ansariel>
|
||||
|
||||
// <FS:Beq> Share to Primfeed
|
||||
void LLPanelSnapshotOptions::onSendToPrimfeed()
|
||||
{
|
||||
LLFloaterReg::hideInstance("snapshot");
|
||||
|
||||
auto* primfeed_floater = dynamic_cast<FSFloaterPrimfeed*>(LLFloaterReg::getInstance("primfeed"));
|
||||
if (primfeed_floater)
|
||||
{
|
||||
primfeed_floater->showPhotoPanel();
|
||||
}
|
||||
LLFloaterReg::showInstance("primfeed");
|
||||
}
|
||||
// </FS:Beq>
|
||||
|
|
@ -36,6 +36,7 @@
|
|||
#include "llfloaterperms.h"
|
||||
#include "llfloaterreg.h"
|
||||
#include "llfloaterflickr.h" // <FS:Ansariel> Share to Flickr
|
||||
#include "fsfloaterprimfeed.h" // <FS:Beq> Share to Primfeed
|
||||
#include "llimagefilter.h"
|
||||
#include "llimagefiltersmanager.h"
|
||||
#include "llimagebmp.h"
|
||||
|
|
|
|||
|
|
@ -225,6 +225,7 @@
|
|||
#include "lggbeamcolormapfloater.h"
|
||||
#include "lggbeammapfloater.h"
|
||||
#include "llfloaterdisplayname.h"
|
||||
#include "fsfloaterprimfeed.h"
|
||||
#include "llfloaterflickr.h"
|
||||
#include "llfloaterscriptrecover.h"
|
||||
#include "llfloatersearchreplace.h"
|
||||
|
|
@ -630,6 +631,7 @@ void LLViewerFloaterReg::registerFloaters()
|
|||
LLFloaterReg::add("export_collada", "floater_export_collada.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<ColladaExportFloater>);
|
||||
LLFloaterReg::add("delete_queue", "floater_script_queue.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterDeleteQueue>);
|
||||
LLFloaterReg::add("flickr", "floater_flickr.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterFlickr>);
|
||||
LLFloaterReg::add("primfeed", "floater_primfeed.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<FSFloaterPrimfeed>);
|
||||
LLFloaterReg::add("fs_asset_blacklist", "floater_fs_asset_blacklist.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<FSFloaterAssetBlacklist>);
|
||||
LLFloaterReg::add("fs_avatar_render_settings", "floater_fs_avatar_render_settings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<FSFloaterAvatarRenderSettings>);
|
||||
LLFloaterReg::add("fs_blocklist", "floater_fs_blocklist.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<FSFloaterBlocklist>);
|
||||
|
|
|
|||
|
|
@ -2757,6 +2757,28 @@ class LLAdvancedCompressFileTest : public view_listener_t
|
|||
}
|
||||
};
|
||||
|
||||
// <FS:Beq> Primfeed integration test functions (can be removed when the feature is stable)
|
||||
///////////////////
|
||||
// PRIMFEED AUTH //
|
||||
///////////////////
|
||||
#include "fsprimfeedauth.h"
|
||||
class LLAdvancedPrimfeedAuth : public view_listener_t
|
||||
{
|
||||
bool handleEvent(const LLSD& userdata)
|
||||
{
|
||||
FSPrimfeedAuth::initiateAuthRequest();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
class LLAdvancedPrimfeedAuthReset : public view_listener_t
|
||||
{
|
||||
bool handleEvent(const LLSD& userdata)
|
||||
{
|
||||
FSPrimfeedAuth::resetAuthStatus();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
// </FS:Beq>
|
||||
|
||||
/////////////////////////
|
||||
// SHOW DEBUG SETTINGS //
|
||||
|
|
@ -12809,6 +12831,8 @@ void initialize_menus()
|
|||
view_listener_t::addMenu(new LLAdvancedCheckShowObjectUpdates(), "Advanced.CheckShowObjectUpdates");
|
||||
view_listener_t::addMenu(new LLAdvancedCompressImage(), "Advanced.CompressImage");
|
||||
view_listener_t::addMenu(new LLAdvancedCompressFileTest(), "Advanced.CompressFileTest");
|
||||
view_listener_t::addMenu(new LLAdvancedPrimfeedAuth(), "Advanced.PrimfeedAuth");
|
||||
view_listener_t::addMenu(new LLAdvancedPrimfeedAuthReset(), "Advanced.PrimfeedAuthReset");
|
||||
view_listener_t::addMenu(new LLAdvancedShowDebugSettings(), "Advanced.ShowDebugSettings");
|
||||
view_listener_t::addMenu(new LLAdvancedEnableViewAdminOptions(), "Advanced.EnableViewAdminOptions");
|
||||
view_listener_t::addMenu(new LLAdvancedToggleViewAdminOptions(), "Advanced.ToggleViewAdminOptions");
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@
|
|||
#include "llfloatertools.h"
|
||||
#include "llfloatersnapshot.h" // <FS:Beq/> for snapshotFrame
|
||||
#include "llfloaterflickr.h" // <FS:Beq/> for snapshotFrame
|
||||
#include "fsfloaterprimfeed.h" // <FS:Beq/> for snapshotFrame
|
||||
#include "llsnapshotlivepreview.h" // <FS:Beq/> for snapshotFrame
|
||||
// #include "llpanelface.h" // <FS:Zi> switchable edit texture/materials panel - include not needed
|
||||
#include "llpathfindingpathtool.h"
|
||||
|
|
@ -8047,12 +8048,12 @@ bool LLPipeline::renderSnapshotFrame(LLRenderTarget* src, LLRenderTarget* dst)
|
|||
}
|
||||
const bool simple_snapshot_visible = LLFloaterReg::instanceVisible("simple_snapshot");
|
||||
const bool flickr_snapshot_visible = LLFloaterReg::instanceVisible("flickr");
|
||||
const bool primfeed_snapshot_visible = LLFloaterReg::instanceVisible("primfeed"); // <FS:Beq/> Primfeed integration
|
||||
const bool snapshot_visible = LLFloaterReg::instanceVisible("snapshot");
|
||||
const bool any_snapshot_visible = simple_snapshot_visible || flickr_snapshot_visible || snapshot_visible;
|
||||
const bool any_snapshot_visible = simple_snapshot_visible || flickr_snapshot_visible || primfeed_snapshot_visible || snapshot_visible; // <FS:Beq/> Primfeed integration
|
||||
if (!show_frame || !any_snapshot_visible || !gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI))
|
||||
{
|
||||
return false;
|
||||
|
||||
}
|
||||
LLSnapshotLivePreview * previewView = nullptr;
|
||||
if (snapshot_visible)
|
||||
|
|
@ -8066,6 +8067,13 @@ bool LLPipeline::renderSnapshotFrame(LLRenderTarget* src, LLRenderTarget* dst)
|
|||
auto * floater = dynamic_cast<LLFloaterFlickr*>(LLFloaterReg::findInstance("flickr"));
|
||||
previewView = floater->getPreviewView();
|
||||
}
|
||||
// <FS:Beq> Primfeed integration
|
||||
if (primfeed_snapshot_visible && !previewView)
|
||||
{
|
||||
auto * floater = dynamic_cast<FSFloaterPrimfeed*>(LLFloaterReg::findInstance("primfeed"));
|
||||
previewView = floater->getPreviewView();
|
||||
}
|
||||
// </FS:Beq>
|
||||
if(!previewView)
|
||||
{
|
||||
return false;
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
|
|
@ -166,6 +166,7 @@ with the same filename but different name
|
|||
<texture name="Command_Places_Icon" file_name="toolbar_icons/places.png" preload="true" />
|
||||
<texture name="Command_Poser_Icon" file_name="toolbar_icons/poser.png" preload="true" /> <!-- FS:AR FIRE-30873 -->
|
||||
<texture name="Command_Preferences_Icon" file_name="toolbar_icons/preferences.png" preload="true" />
|
||||
<texture name="Command_Primfeed_Icon" file_name="icons/primfeed_white.png" preload="true" /> <!-- FS:Beq Primfeed support -->
|
||||
<texture name="Command_Profile_Icon" file_name="toolbar_icons/profile.png" preload="true" />
|
||||
<texture name="Command_Report_Abuse_Icon" file_name="toolbar_icons/report_abuse.png" preload="true" />
|
||||
<texture name="Command_Search_Icon" file_name="toolbar_icons/search.png" preload="true" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<floater
|
||||
positioning="cascading"
|
||||
can_close="true"
|
||||
can_resize="false"
|
||||
help_topic="floater_primfeed"
|
||||
layout="topleft"
|
||||
name="floater_primfeed"
|
||||
save_rect="true"
|
||||
single_instance="true"
|
||||
reuse_instance="true"
|
||||
title="Share to Primfeed"
|
||||
height="600"
|
||||
width="272">
|
||||
<panel
|
||||
height="590"
|
||||
width="272"
|
||||
visible="true"
|
||||
name="background"
|
||||
follows="all"
|
||||
top="0"
|
||||
left="0">
|
||||
<tab_container
|
||||
name="tabs"
|
||||
tab_group="1"
|
||||
tab_min_width="70"
|
||||
tab_height="21"
|
||||
tab_position="top"
|
||||
top="7"
|
||||
height="570"
|
||||
follows="all"
|
||||
halign="center">
|
||||
<panel
|
||||
filename="panel_primfeed_photo.xml"
|
||||
class="fsprimfeedphotopanel"
|
||||
follows="all"
|
||||
label="Photo"
|
||||
name="panel_primfeed_photo"/>
|
||||
<panel
|
||||
filename="panel_primfeed_account.xml"
|
||||
class="fsprimfeedaccountpanel"
|
||||
follows="all"
|
||||
label="Account"
|
||||
name="panel_primfeed_account"/>
|
||||
</tab_container>
|
||||
<panel
|
||||
name="connection_status_panel"
|
||||
follows="left|bottom|right"
|
||||
height="24">
|
||||
<text
|
||||
name="connection_error_text"
|
||||
type="string"
|
||||
follows="left|bottom|right"
|
||||
bottom="-5"
|
||||
left="10"
|
||||
width="250"
|
||||
height="20"
|
||||
wrap="true"
|
||||
halign="left"
|
||||
valign="center"
|
||||
text_color="DrYellow"
|
||||
font="SansSerif">
|
||||
Error
|
||||
</text>
|
||||
<loading_indicator
|
||||
follows="left|bottom|right"
|
||||
height="24"
|
||||
width="24"
|
||||
name="connection_loading_indicator"
|
||||
top_delta="-2"
|
||||
left="10"
|
||||
visible="true"/>
|
||||
<text
|
||||
name="connection_loading_text"
|
||||
type="string"
|
||||
follows="left|bottom|right"
|
||||
top_delta="2"
|
||||
left_pad="5"
|
||||
width="250"
|
||||
height="20"
|
||||
wrap="true"
|
||||
halign="left"
|
||||
valign="center"
|
||||
text_color="EmphasisColor"
|
||||
font="SansSerif">
|
||||
Loading...
|
||||
</text>
|
||||
</panel>
|
||||
</panel>
|
||||
</floater>
|
||||
|
|
@ -5669,6 +5669,18 @@
|
|||
<menu_item_call.on_click
|
||||
function="Advanced.CompressFileTest" />
|
||||
</menu_item_call>
|
||||
<menu_item_call
|
||||
label="PrimFeed Auth Test"
|
||||
name="primfeed_auth_test">
|
||||
<menu_item_call.on_click
|
||||
function="Advanced.PrimfeedAuth" />
|
||||
</menu_item_call>
|
||||
<menu_item_call
|
||||
label="PrimFeed Auth Reset"
|
||||
name="primfeed_auth_clear">
|
||||
<menu_item_call.on_click
|
||||
function="Advanced.PrimfeedAuthReset" />
|
||||
</menu_item_call>
|
||||
|
||||
<menu_item_call
|
||||
label="Enable Visual Leak Detector"
|
||||
|
|
|
|||
|
|
@ -7911,6 +7911,13 @@ Please select at least one type of content to search (General, Moderate, or Adul
|
|||
type="notifytip">
|
||||
[MESSAGE]
|
||||
</notification>
|
||||
|
||||
<notification
|
||||
icon="notify.tga"
|
||||
name="PrimfeedConnect"
|
||||
type="notifytip">
|
||||
[MESSAGE]
|
||||
</notification>
|
||||
|
||||
<notification
|
||||
icon="notify.tga"
|
||||
|
|
@ -13962,6 +13969,12 @@ Flickr verification failed. Please try again, and be sure to double check the ve
|
|||
name="ExodusFlickrUploadComplete"
|
||||
type="notifytip">
|
||||
Your snapshot can now be viewed [https://www.flickr.com/photos/me/[ID] here].
|
||||
</notification>
|
||||
<notification
|
||||
icon="notifytip.tga"
|
||||
name="FSPrimfeedUploadComplete"
|
||||
type="notifytip">
|
||||
Your primfeed post can now be viewed [[PF_POSTURL] here].
|
||||
</notification>
|
||||
<!-- </FS:TS> FIRE-5453 -->
|
||||
|
||||
|
|
@ -14676,4 +14689,60 @@ https://wiki.firestormviewer.org/antivirus_whitelisting
|
|||
yestext="Okay"/>
|
||||
</notification>
|
||||
|
||||
<notification
|
||||
icon="alertmodal.tga"
|
||||
name="PrimfeedLoginRequestFailed"
|
||||
persist="false"
|
||||
log_to_im="true"
|
||||
type="notify">
|
||||
Login request denied by Primfeed.
|
||||
</notification>
|
||||
<notification
|
||||
icon="alertmodal.tga"
|
||||
name="PrimfeedAuthorisationFailed"
|
||||
persist="false"
|
||||
tag="fail"
|
||||
type="alertmodal">
|
||||
Primfeed authorisation failed. The authorisation sequence was not completed.
|
||||
</notification>
|
||||
<notification
|
||||
icon="alertmodal.tga"
|
||||
name="PrimfeedAuthorisationAlreadyInProgress"
|
||||
persist="false"
|
||||
tag="fail"
|
||||
type="alertmodal">
|
||||
Primfeed authorisation is already in progress. Please complete the primfeed authorisation in your web browser before trying again.
|
||||
</notification>
|
||||
<notification
|
||||
icon="alertmodal.tga"
|
||||
name="PrimfeedAuthorisationSuccessful"
|
||||
persist="false"
|
||||
tag="success"
|
||||
type="alertmodal">
|
||||
Primfeed authorisation completed. You may now post images to Primfeed.
|
||||
</notification>
|
||||
<notification
|
||||
icon="alertmodal.tga"
|
||||
name="PrimfeedValidateFailed"
|
||||
persist="false"
|
||||
tag="fail"
|
||||
type="alertmodal">
|
||||
Primfeed user validation failed. Primfeed did not recognise this account, or the login failed.
|
||||
</notification>
|
||||
<notification
|
||||
icon="alertmodal.tga"
|
||||
name="PrimfeedAlreadyAuthorized"
|
||||
persist="false"
|
||||
tag="success"
|
||||
type="alertmodal">
|
||||
You have already linked this account to Primfeed. Use the reset button if you wish to start over.
|
||||
</notification>
|
||||
<notification
|
||||
icon="alertmodal.tga"
|
||||
name="PrimfeedUserStatusFailed"
|
||||
persist="false"
|
||||
tag="fail"
|
||||
type="alertmodal">
|
||||
Primfeed user login successful, but status checks have failed. Please check the Primfeed is working.
|
||||
</notification>
|
||||
</notifications>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
<panel
|
||||
height="540"
|
||||
width="272"
|
||||
layout="topleft"
|
||||
name="panel_primfeed_account">
|
||||
<string
|
||||
name="primfeed_connected"
|
||||
value="You are connected to Primfeed as:" />
|
||||
<string
|
||||
name="primfeed_disconnected"
|
||||
value="Not connected to Primfeed" />
|
||||
<string
|
||||
name="primfeed_plan_unknown"
|
||||
value="Unknown" />
|
||||
<text
|
||||
layout="topleft"
|
||||
length="1"
|
||||
follows="top|left"
|
||||
font="SansSerif"
|
||||
height="16"
|
||||
left="10"
|
||||
name="connected_as_label"
|
||||
top="5"
|
||||
type="string">
|
||||
Not connected to Primfeed.
|
||||
</text>
|
||||
<text
|
||||
layout="topleft"
|
||||
top_pad="2"
|
||||
length="1"
|
||||
follows="top|left"
|
||||
font="SansSerif"
|
||||
height="16"
|
||||
left="10"
|
||||
name="primfeed_account_name"
|
||||
parse_urls="true"
|
||||
type="string"/>
|
||||
<text
|
||||
layout="topleft"
|
||||
length="1"
|
||||
follows="top|left"
|
||||
font="SansSerif"
|
||||
height="16"
|
||||
left="10"
|
||||
name="primfeed_account_plan_label"
|
||||
top_pad="2"
|
||||
type="string">
|
||||
Account type:
|
||||
</text>
|
||||
<text
|
||||
layout="topleft"
|
||||
top_pad="2"
|
||||
length="1"
|
||||
follows="top|left"
|
||||
font="SansSerif"
|
||||
height="16"
|
||||
left="10"
|
||||
name="primfeed_account_plan"
|
||||
parse_urls="true"
|
||||
type="string"/>
|
||||
<panel
|
||||
layout="topleft"
|
||||
name="panel_buttons"
|
||||
height="345"
|
||||
left="0">
|
||||
<button
|
||||
layout="topleft"
|
||||
follows="left|top|right"
|
||||
top_pad="9"
|
||||
visible="true"
|
||||
left="10"
|
||||
right="-10"
|
||||
height="23"
|
||||
label="Connect..."
|
||||
name="connect_btn"
|
||||
width="210">
|
||||
<commit_callback function="SocialSharing.Connect"/>
|
||||
</button>
|
||||
|
||||
<button
|
||||
layout="topleft"
|
||||
follows="left|top|right"
|
||||
top_delta="0"
|
||||
left="10"
|
||||
right="-10"
|
||||
height="23"
|
||||
label="Disconnect"
|
||||
name="disconnect_btn"
|
||||
width="210"
|
||||
visible="false">
|
||||
<commit_callback function="SocialSharing.Disconnect"/>
|
||||
</button>
|
||||
<text
|
||||
layout="topleft"
|
||||
length="1"
|
||||
follows="top|left"
|
||||
height="16"
|
||||
left="10"
|
||||
name="account_learn_more_label"
|
||||
top_pad="5"
|
||||
type="string">
|
||||
[https://docs.primfeed.com Learn more about Primfeed]
|
||||
</text>
|
||||
</panel>
|
||||
</panel>
|
||||
|
|
@ -0,0 +1,350 @@
|
|||
<panel
|
||||
height="540"
|
||||
width="272"
|
||||
follows="all"
|
||||
layout="topleft"
|
||||
name="panel_primfeed_photo">
|
||||
<combo_box
|
||||
control_name="FSPrimfeedPhotoResolution"
|
||||
follows="left|top"
|
||||
layout="topleft"
|
||||
top="5"
|
||||
left="10"
|
||||
name="resolution_combobox"
|
||||
tool_tip="Image resolution"
|
||||
height="21"
|
||||
width="124">
|
||||
<combo_box.item
|
||||
label="Current Window"
|
||||
name="CurrentWindow"
|
||||
value="[i0,i0]" />
|
||||
<combo_box.item
|
||||
label="320x240"
|
||||
name="320x240"
|
||||
value="[i320,i240]" />
|
||||
<combo_box.item
|
||||
label="640x480"
|
||||
name="640x480"
|
||||
value="[i640,i480]" />
|
||||
<combo_box.item
|
||||
label="800x600"
|
||||
name="800x600"
|
||||
value="[i800,i600]" />
|
||||
<combo_box.item
|
||||
label="1024x768"
|
||||
name="1024x768"
|
||||
value="[i1024,i768]" />
|
||||
<combo_box.item
|
||||
label="1280x1024"
|
||||
name="1280x1024"
|
||||
value="[i1280,i1024]" />
|
||||
<combo_box.item
|
||||
label="1600x1200"
|
||||
name="1600x1200"
|
||||
value="[i1600,i1200]" />
|
||||
<combo_box.item
|
||||
label="Custom"
|
||||
name="Custom"
|
||||
value="[i-1,i-1]" />
|
||||
</combo_box>
|
||||
<combo_box
|
||||
follows="left|top"
|
||||
layout="topleft"
|
||||
name="filters_combobox"
|
||||
tool_tip="Image filters"
|
||||
top_delta="0"
|
||||
left_pad="4"
|
||||
height="21"
|
||||
width="124">
|
||||
<combo_box.item
|
||||
label="No Filter"
|
||||
name="NoFilter"
|
||||
value="NoFilter" />
|
||||
</combo_box>
|
||||
<spinner
|
||||
allow_text_entry="false"
|
||||
decimal_digits="0"
|
||||
follows="left|top"
|
||||
height="20"
|
||||
increment="32"
|
||||
layout="topleft"
|
||||
left="10"
|
||||
max_val="6016"
|
||||
min_val="32"
|
||||
name="custom_snapshot_width"
|
||||
top_pad="7"
|
||||
width="54" />
|
||||
<text
|
||||
length="1"
|
||||
follows="top|left|right"
|
||||
layout="topleft"
|
||||
height="16"
|
||||
left_pad="3"
|
||||
name="spinner_x_lbl"
|
||||
top_delta="3"
|
||||
width="8"
|
||||
type="string">
|
||||
x
|
||||
</text>
|
||||
<spinner
|
||||
allow_text_entry="false"
|
||||
decimal_digits="0"
|
||||
follows="left|top"
|
||||
height="20"
|
||||
increment="32"
|
||||
label=""
|
||||
label_width="0"
|
||||
layout="topleft"
|
||||
left_pad="0"
|
||||
max_val="6016"
|
||||
min_val="32"
|
||||
name="custom_snapshot_height"
|
||||
top_delta="-3"
|
||||
width="54" />
|
||||
<check_box
|
||||
follows="left|top"
|
||||
layout="topleft"
|
||||
initial_value="true"
|
||||
label="Keep Aspect ratio"
|
||||
name="keep_aspect_ratio"
|
||||
left_pad="4"
|
||||
height="16"
|
||||
top_delta="4" />
|
||||
<panel
|
||||
height="150"
|
||||
width="250"
|
||||
visible="true"
|
||||
name="thumbnail_placeholder"
|
||||
top_pad="3"
|
||||
follows="left|top|right"
|
||||
layout="topleft"
|
||||
right="-10"
|
||||
left="10">
|
||||
</panel>
|
||||
<text
|
||||
follows="left|top"
|
||||
layout="topleft"
|
||||
font="SansSerif"
|
||||
text_color="EmphasisColor"
|
||||
height="14"
|
||||
top_pad="2"
|
||||
left="10"
|
||||
length="1"
|
||||
halign="center"
|
||||
name="working_lbl"
|
||||
type="string"
|
||||
visible="true"
|
||||
width="251">
|
||||
Refreshing...
|
||||
</text>
|
||||
<check_box
|
||||
control_name="FSSnapshotShowCaptureFrame"
|
||||
label="Show capture frame"
|
||||
tool_tip="Show a frame on-screen that surrounds the areas of the snapshot. Parts of the scene that are outside of the snapshot will be de-saturated and slightly blurred."
|
||||
layout="topleft"
|
||||
left="10"
|
||||
top_pad="7"
|
||||
width="124"
|
||||
name="show_frame" />
|
||||
<check_box
|
||||
enabled_control="FSSnapshotShowCaptureFrame"
|
||||
control_name="FSSnapshotShowGuides"
|
||||
label="Framing guide"
|
||||
tool_tip="Show framing guide (rule of thirds) inside the snapshot frame."
|
||||
layout="topleft"
|
||||
left_pad="10"
|
||||
width="60"
|
||||
name="show_guides" />
|
||||
<view_border
|
||||
bevel_style="in"
|
||||
follows="left|top"
|
||||
layout="topleft"
|
||||
height="1"
|
||||
left="10"
|
||||
name="refresh_border"
|
||||
width="250"
|
||||
top_pad="0" />
|
||||
<button
|
||||
follows="left|top"
|
||||
layout="topleft"
|
||||
height="23"
|
||||
label="Refresh"
|
||||
left="10"
|
||||
top_pad="5"
|
||||
name="new_snapshot_btn"
|
||||
tool_tip="Click to refresh"
|
||||
visible="true"
|
||||
width="100">
|
||||
<button.commit_callback function="SocialSharing.RefreshPhoto" />
|
||||
</button>
|
||||
<button
|
||||
follows="right|top"
|
||||
layout="topleft"
|
||||
height="23"
|
||||
label="Preview"
|
||||
right="-10"
|
||||
top_delta="0"
|
||||
name="big_preview_btn"
|
||||
tool_tip="Click to toggle preview"
|
||||
is_toggle="true"
|
||||
visible="true"
|
||||
width="100">
|
||||
<button.commit_callback function="SocialSharing.BigPreview" />
|
||||
</button>
|
||||
<text
|
||||
length="1"
|
||||
follows="top|left|right"
|
||||
layout="topleft"
|
||||
font="SansSerif"
|
||||
height="16"
|
||||
left="10"
|
||||
right="-10"
|
||||
name="description_label"
|
||||
top_pad="5"
|
||||
width="25"
|
||||
type="string">
|
||||
Description:
|
||||
</text>
|
||||
<text_editor
|
||||
follows="left|top"
|
||||
layout="topleft"
|
||||
height="100"
|
||||
width="249"
|
||||
left="10"
|
||||
length="1"
|
||||
top_pad="0"
|
||||
max_length="700"
|
||||
name="photo_description"
|
||||
spellcheck="true"
|
||||
type="string"
|
||||
word_wrap="true">
|
||||
</text_editor>
|
||||
<check_box
|
||||
follows="left|top"
|
||||
layout="topleft"
|
||||
initial_value="true"
|
||||
label="Include location"
|
||||
name="add_location_cb"
|
||||
left="9"
|
||||
height="16"
|
||||
top_pad="8" />
|
||||
<check_box
|
||||
control_name="FSPrimfeedAddToPublicGallery"
|
||||
follows="top|left"
|
||||
layout="topleft"
|
||||
initial_value="false"
|
||||
label="Add to public gallery?"
|
||||
name="primfeed_add_to_public_gallery"
|
||||
left="9"
|
||||
height="16"
|
||||
top_pad="8" />
|
||||
<button
|
||||
follows="top|left"
|
||||
layout="topleft"
|
||||
height="16"
|
||||
image_pressed="Info_Press"
|
||||
image_unselected="Info_Over"
|
||||
name="info_btn_pub_gallery"
|
||||
right="-3"
|
||||
top_delta="-2"
|
||||
width="16"
|
||||
commit_callback.function="Primfeed.Info"
|
||||
commit_callback.parameter="https://docs.primfeed.com/featured-content/public-gallery"
|
||||
/>
|
||||
<check_box
|
||||
enabled_control="FSPrimfeedAddToPublicGallery"
|
||||
follows="top|left"
|
||||
layout="topleft"
|
||||
initial_value="false"
|
||||
label="Is this commercial content?"
|
||||
name="primfeed_commercial_content"
|
||||
left="9"
|
||||
height="16"
|
||||
top_pad="8" />
|
||||
<button
|
||||
follows="top|left"
|
||||
layout="topleft"
|
||||
height="16"
|
||||
image_pressed="Info_Press"
|
||||
image_unselected="Info_Over"
|
||||
right="-3"
|
||||
top_delta="-2"
|
||||
name="info_btn_commercial_content"
|
||||
width="16"
|
||||
commit_callback.function="Primfeed.Info"
|
||||
commit_callback.parameter="https://docs.primfeed.com/legal/terms-of-service#commercial-content"
|
||||
/>
|
||||
<combo_box
|
||||
control_name="FSPrimfeedPhotoRating"
|
||||
follows="left|top"
|
||||
layout="topleft"
|
||||
top_pad="8"
|
||||
left="10"
|
||||
name="rating_combobox"
|
||||
tool_tip="Primfeed content rating"
|
||||
height="21"
|
||||
width="235">
|
||||
<combo_box.item
|
||||
label="General"
|
||||
name="GeneralRating"
|
||||
value="1" />
|
||||
<combo_box.item
|
||||
label="Moderate"
|
||||
name="ModerateRating"
|
||||
value="2" />
|
||||
<combo_box.item
|
||||
label="Adult"
|
||||
name="AdultRating"
|
||||
value="3" />
|
||||
<combo_box.item
|
||||
label="Adult+"
|
||||
name="AdultPlusRating"
|
||||
value="4" />
|
||||
</combo_box>
|
||||
<button
|
||||
follows="top|left"
|
||||
layout="topleft"
|
||||
height="16"
|
||||
image_pressed="Info_Press"
|
||||
image_unselected="Info_Over"
|
||||
right="-3"
|
||||
name="info_btn_ratings"
|
||||
top_delta="0"
|
||||
width="16"
|
||||
commit_callback.function="Primfeed.Info"
|
||||
commit_callback.parameter="https://docs.primfeed.com/help-and-faq/maturity-ratings"
|
||||
/>
|
||||
<check_box
|
||||
control_name="FSPrimfeedOpenURLOnPost"
|
||||
follows="top|left"
|
||||
layout="topleft"
|
||||
initial_value="false"
|
||||
label="Open in browser after posting?"
|
||||
tool_tip="Automatically open the Primfeed post in your web browser after posting."
|
||||
name="primfeed_open_url_on_post"
|
||||
left="9"
|
||||
height="16"
|
||||
top_pad="8" />
|
||||
<button
|
||||
follows="left|top"
|
||||
layout="topleft"
|
||||
top_pad="8"
|
||||
left="10"
|
||||
height="23"
|
||||
label="Share"
|
||||
name="post_photo_btn"
|
||||
width="100">
|
||||
<button.commit_callback function="SocialSharing.SendPhoto" />
|
||||
</button>
|
||||
<button
|
||||
follows="right|top"
|
||||
layout="topleft"
|
||||
height="23"
|
||||
label="Cancel"
|
||||
name="cancel_photo_btn"
|
||||
right="-10"
|
||||
top_delta="0"
|
||||
width="100">
|
||||
<button.commit_callback function="SocialSharing.Cancel" />
|
||||
</button>
|
||||
</panel>
|
||||
|
|
@ -52,6 +52,7 @@
|
|||
height="22"
|
||||
image_overlay="Snapshot_Inventory"
|
||||
image_overlay_alignment="left"
|
||||
image_overlay_width="16"
|
||||
image_top_pad="-1"
|
||||
imgoverlay_label_space="10"
|
||||
label="Save to Inventory"
|
||||
|
|
@ -111,6 +112,30 @@
|
|||
function="Snapshot.SendToFlickr"/>
|
||||
</button>
|
||||
</layout_panel>
|
||||
<layout_panel
|
||||
auto_resize="false"
|
||||
user_resize="false"
|
||||
top_pad="0"
|
||||
height="22"
|
||||
name="lp_primfeed">
|
||||
<button
|
||||
follows="left|top"
|
||||
font="SansSerif"
|
||||
halign="left"
|
||||
height="22"
|
||||
image_overlay="Command_Primfeed_Icon"
|
||||
image_overlay_alignment="left"
|
||||
image_top_pad="0"
|
||||
imgoverlay_label_space="10"
|
||||
label="Share to Primfeed"
|
||||
layout="topleft"
|
||||
left="9"
|
||||
name="send_to_primfeed_btn"
|
||||
top_pad="0">
|
||||
<button.commit_callback
|
||||
function="Snapshot.SendToPrimfeed"/>
|
||||
</button>
|
||||
</layout_panel>
|
||||
<layout_panel
|
||||
auto_resize="false"
|
||||
user_resize="false"
|
||||
|
|
|
|||
|
|
@ -238,6 +238,10 @@ If you feel this is an error, please contact support@secondlife.com</string>
|
|||
<string name="SocialFlickrErrorConnecting">Problem connecting to Flickr</string>
|
||||
<string name="SocialFlickrErrorPosting">Problem posting to Flickr</string>
|
||||
<string name="SocialFlickrErrorDisconnecting">Problem disconnecting from Flickr</string>
|
||||
<string name="SocialPrimfeedConnecting">Connecting to Primfeed...</string>
|
||||
<string name="SocialPrimfeedNotAuthorized">Not Authorized...</string>
|
||||
<string name="SocialPrimfeedPosting">Posting...</string>
|
||||
<string name="SocialPrimfeedErrorPosting">Problem posting to Primfeed</string>
|
||||
|
||||
<!-- SLShare: User Friendly Filter Names Translation -->
|
||||
<string name="BlackAndWhite">Black & White</string>
|
||||
|
|
@ -2767,6 +2771,8 @@ name="Command_360_Capture_Label">360° Snapshot</string>
|
|||
<string name="Command_Beacons_Label">Beacons</string>
|
||||
<string name="Command_Poser_Label">Poser</string>
|
||||
<string name="Command_Poser_Tooltip">Pose your avatar and animated objects</string>
|
||||
<string name="Command_Primfeed_Label">Primfeed</string>
|
||||
<string name="Command_Primfeed_Tooltip">Post directly to your Primfeed account.</string>
|
||||
|
||||
<string
|
||||
name="Command_360_Capture_Tooltip">Capture a 360° equirectangular image</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue