How to add positioning enrichment to media item in Google Photos API? - google-api

Reading Google Photos API documentation on how to add position media items inside the album I can't understand what should be the request body. https://developers.google.com/photos/library/guides/add-enrichments#supported-positioning
I want to move mediaItem-1 to be before mediaItem-2 in album album-1. How my request body should look like?
POST https://photoslibrary.googleapis.com/v1/albums/album-1:addEnrichment
{
"newEnrichmentItem": {
enrichment-to-be-added // What goes here?
},
"albumPosition": {
"position": "after-media-item",
"relativeMediaItemId": "mediaItem-2"
}
}
EDIT: I don't want to reupload a media item to the cloud, so there will be only one version of the same media item.

A position can only be specified when creating a media item or adding enrichments. Existing media items in an album can't be reorganized, so it's important to set the position of an item when it's being added.
Reference:
https://developers.google.com/photos/library/guides/add-enrichments#intro-positions
The method you are using is for Album Enrichment.
Enrichment Types:
Text enrichments
A text enrichment is a plain text string that can be inserted to annotate the album.
Location enrichments
A location enrichment is a marker and the name of the place that can be inserted to annotate a location.
Map enrichments
A map enrichment is a map with a specified origin and destination that can be inserted in the album.
Additional References:
https://developers.google.com/photos/library/reference/rest/v1/albums/addEnrichment?hl=en_US#request-body
Sample media item creation with position:
https://developers.google.com/photos/library/reference/rest/v1/mediaItems/batchCreate#request-body
{
"albumId": string,
"newMediaItems": [
{
object (NewMediaItem)
}
],
"albumPosition": {
object (AlbumPosition)
}
}
albumPosition
Position in the album where the media items are added. If not specified, the media items are added to the end of the album (as per the default value, that is, LAST_IN_ALBUM). The request fails if this field is set and the albumId is not specified. The request will also fail if you set the field and are not the owner of the shared album.
(UPDATE)
Workaround:
Delete mediaItem-2 in album-1
Create mediaItem-2 in album-1 with position parameter which will place mediaItem-2 after mediaItem-1
https://developers.google.com/photos/library/reference/rest/v1/AlbumPosition#PositionType
Your request body for create media item:
{
"albumId": album-1,
"newMediaItems": [
{
object (mediaItem-2)
}
],
"albumPosition": {
"position": AFTER_MEDIA_ITEM.
"relativeMediaItemId": mediaItem-1
}
}

Related

Given a key, how do I read the value from a table via the API?

Imagine I have a move module that looks like this.
Move.toml
[package]
name = 'friends'
version = '1.0.0'
[dependencies.AptosFramework]
git = 'https://github.com/aptos-labs/aptos-core.git'
rev = 'testnet'
subdir = 'aptos-move/framework/aptos-framework'
[addresses]
friends = "81e2e2499407693c81fe65c86405ca70df529438339d9da7a6fc2520142b591e"
sources/nicknames.move
module friends::nicknames {
use std::error;
use std::signer;
use std::string::String;
use aptos_std::table::{Self, Table};
const ENOT_INITIALIZED: u64 = 0;
struct Nicknames has key {
// A map of friends' nicknames to wallet addresses.
nickname_to_addr: Table<String, address>
}
/// Initialize Inner to the caller's account.
public entry fun initialize(account: &signer) {
let nicknames = Nicknames {
nickname_to_addr: table::new(),
};
move_to(account, nicknames);
}
/// Initialize Inner to the caller's account.
public entry fun add(account: &signer, nickname: String, friend_addr: address) acquires Nicknames {
let signer_addr = signer::address_of(account);
assert!(exists<Nicknames>(signer_addr), error::not_found(ENOT_INITIALIZED));
let nickname_to_addr = &mut borrow_global_mut<Nicknames>(signer_addr).nickname_to_addr;
table::add(nickname_to_addr, nickname, friend_addr);
}
}
I then published the module (to testnet), initialized Nicknames to my account, and then added an entry:
aptos move publish
aptos move run --function-id 81e2e2499407693c81fe65c86405ca70df529438339d9da7a6fc2520142b591e::nicknames::initialize
aptos move run --function-id 81e2e2499407693c81fe65c86405ca70df529438339d9da7a6fc2520142b591e::nicknames::add --args string:dport address:81e2e2499407693c81fe65c86405ca70df529438339d9da7a6fc2520142b591e
Now that my table is on-chain with some data, how would I go about reading the value of the dport key. I think I can use the API for this?
You're right that you can use the API for this! First let's get some information about your table.
Let's look at the resource you've deployed to your account from the above Move module. First let's construct the struct tag (aka resource ID / handle), it looks like this:
<account_address>::<module>::<struct_name>
In your case:
0x81e2e2499407693c81fe65c86405ca70df529438339d9da7a6fc2520142b591e::nicknames::Nicknames
Because there can only be one of each resource in an account in the Aptos blockchain, we can use this to uniquely identify the resource in your account. Using this we can then get the table handle. A table handle is a globally unique ID (so, not just within the bounds of your account) that points to that specific table. We need that to make any further queries, so let's get that first:
$ curl https://fullnode.testnet.aptoslabs.com/v1/accounts/0x81e2e2499407693c81fe65c86405ca70df529438339d9da7a6fc2520142b591e/resource/0x81e2e2499407693c81fe65c86405ca70df529438339d9da7a6fc2520142b591e::nicknames::Nicknames | jq .
{
"type": "0x81e2e2499407693c81fe65c86405ca70df529438339d9da7a6fc2520142b591e::nicknames::Nicknames",
"data": {
"nickname_to_addr": {
"handle": "0x64fa842ed2c9da130f0419875e6c101aeea263882fadee3257b13f1bb4d7d41d"
}
}
}
Explaining the above:
Hitting https://fullnode.testnet.aptoslabs.com/v1/accounts/0x81e2e2499407693c81fe65c86405ca70df529438339d9da7a6fc2520142b591e/resource/<name> lets us get a specific resource on an account.
We then request specifically 0x81e2e2499407693c81fe65c86405ca70df529438339d9da7a6fc2520142b591e::nicknames::Nicknames. The two addresses happen to be the same here, but that's just because we're using the same account, another example could be 0x1::aptos_coin::AptosCoin.
We can see the handle there, 0x64fa842ed2c9da130f0419875e6c101aeea263882fadee3257b13f1bb4d7d41d.
Using this handle, we can now query the API:
$ cat query.json
{
"key_type": "0x1::string::String",
"value_type": "address",
"key": "dport"
}
$ curl -H 'Content-Type: application/json' --data-binary "#query.json" https://fullnode.testnet.aptoslabs.com/v1/tables/0x64fa842ed2c9da130f0419875e6c101aeea263882fadee3257b13f1bb4d7d41d/item
"0x81e2e2499407693c81fe65c86405ca70df529438339d9da7a6fc2520142b591e"
Explaining the above:
The request to get an item from a table via the API is a POST request. The data comes from this query.json file.
key_type is the type of the key in the table. You can see from the original declaration of nickname_to_addr that this was 0x1::string::String.
value_type is the type of the value. Its type was address, a special type that isn't deployed at any particular module (hence the lack of <addr>::<module>::.
key is the key that we're querying in the table.
If hypothetically the key used in the table was more complex, like a struct instead of a single value like a string, you could represent that struct as JSON in the request, e.g.
{
"key_type": "0x1::string::String",
"value_type": "address",
"key": {
"first_name": "Ash",
"last_name": "Ketchum",
}
}
This is the extent of what you can do with the API right now. Namely, reading values given you know the key ahead of time. If you want to do the below things, you need to query an indexer:
Iterate through all the keys in a table.
Get the entire table, both keys and values.
I'll write an answer on how to do this later.

Shopify Storefront GraphQL filtering products within a collection

According to this documentation:
https://shopify.dev/custom-storefronts/products/filter-products#query-products-by-type
We should be able to filter products within a collection using collectionByHandle.
I have created a very basic test query in the Shopify GraphiQL App explorer tool. When I run the documented query, it returns all products, not filtering at all. See below:
This looks like a bug with the API right? Or am I missing something basic?
OK this turned out to be a configuration issue. To allow filtering by product type, it needs to be turned on in the admin for your store. If you navigate to:
Online Store > Navigation
... and scroll to the bottom, you will see where you can add allowed filters:
Even if it says your theme doesn't support filters, it will still change the way the API behaves.
I have the same problem, the filters param of products query seem to be ineffective and returning me all the products in the collection.
I can't find the "allowed filters" option.
Currently I'm using the Storefront API as an external app, all works fine except that.
Here the code.
query (
$collectionHandle: String, $product_filters: [ProductFilter!], $nQueryElements: Int
) {
collection(handle: $collectionHandle) {
title
products(first: $nQueryElements filters: $product_filters) {
pageInfo {
hasNextPage
hasPreviousPage
}
edges {
cursor
node {
title
vendor
availableForSale
id
handle
productType
variants(first: 40) {
edges {
node {
selectedOptions {
name
value
}
title
compareAtPriceV2 {
amount
}
image {
id
}
}
}
}
priceRange {
maxVariantPrice {
amount
}
}
images(first: 1) {
edges {
node {
id
url(transform: { maxWidth: 500, maxHeight: 700 })
}
}
}
}
}
}
}
}
const variables = { collectionHandle: this.pageURL, nQueryElements: this.nQueryElements, lastCursor: this.queryCursor.last, firstCursor: this.queryCursor.first, product__filters: [{ productVendor: "ASPESI", },], };
Thanks to who can help.
This can be done now. This post on the Shopify forum explains it perfectly with the latest API.
In case that post gets deleted, I'm going to put the info below:
We can now filter by metafields but through a collection. https://shopify.dev/custom-storefronts/products-collections/filter-products#query-products-by-metafi...
Requirements:
The metafield must have been added as a filter in the "Search &
Discovery app" or the Filters in the Navigation settings.
The metafield must be of one of these types: single_line_text_field,
boolean, numeric_integer, numeric_decimal
The Storefront API used must be 2022-04 or higher. I tested it with
2022-10
The metafield must be exposed to the Storefront API
Knowing that as of today, we have a limit of 5000 products for filters to work in a normal store (see https://help.shopify.com/en/manual/online-store/search-and-discovery/filters)
I decided to test if that restriction applies to the Storefront API, I tested it with a collection with 11769 products and I was able to get filtered results as expected. So it seems that at this stage we don't have this limitation in the Storefront API
.

Should I filter within my flux stores?

This is essentially a matter of best practices for sync vs. async operations in the store/view.
I'm attempting to build a real-time search field for a list of items.
In my current implementation, I request all the items from my server and save them to a store. When a user enters characters into the search field, the view filters the items using the .filter() native function. Unfortunately, this causes some delay (due to the number of items and the complexity of the filter) before the next rendering (which includes the keypressed character displaying in the search field).
My question is: should I instead call an action to initialize the filtering of the items in the store, and update whenever the store is complete? Then, in the meantime, I would be able to render the keypressed character before the filtered results come in.
Is there an intuitive way to prevent/abort a previous, incomplete request to filter when a new one comes in?
Edit:
Here's the new implementation:
The component/view
_onChange() {
this.setState({
items: ItemStore.getFilteredItems()
})
},
handleSearchChange(event) {
this.setState({
searchText: event.target.value,
})
ItemActions.filterItems(event.target.value)
},
render() {...}
The action
filterItems(searchTerm) {
dispatcher.dispatch({
type: FILTER_ITEMS,
searchTerm: searchTerm,
});
}
The store
var _store = {
items: [],
filteredItems: []
}
var filter = function (searchTerm) {...}
...
Dispatcher.register(function (action) {
switch (action.type) {
case FILTER_ITEMS:
filter(action.searchTerm)
ItemStore.emit(CHANGE_EVENT)
break
}
})
Edit 2:
I've ended up adding a setTimeout when dispatching within the action to make it async. I also split the list of items and the search into two different components so that the time required to re-render the list does not affect/block the search field component.
The filtering should not be blocking the keypressed character in the search field. Basically, because the filtering is a heavy operation, treat it as if it were an async HTTP event.
Here's how you should be doing in a Flux world:
Retrieve all items from the server and save them to the store
Each time you type in the search field, set the state for the input field and the component should re-render immediately (https://facebook.github.io/react/docs/forms.html#controlled-components)
At the same time as you set the state of the text, also dispatch an action to filter the results. The filtered results should be put in the store as a separate entity and updated as a result of this action.
When the store updates the filtered results, your component should have them as a prop and re-render automatically (independent of the keypress events)

Fabric.js - Sync object:modified event to another client

Collaboration Mode:
What is the best way to propagate changes from Client #1's canvas to client #2's canvas? Here's how I capture and send events to Socket.io.
$scope.canvas.on('object:modified',function(e) {
Socket.whiteboardMessage({
eventId:'object:modified',
event:e.target.toJSON()
});
});
On the receiver side, this code works splendidly for adding new objects to the screen, but I could not find documentation on how to select and update an existing object in the canvas.
fabric.util.enlivenObjects([e.event], function(objects) {
objects.forEach(function(o) {
$scope.canvas.add(o);
});
});
I did see that Objects have individual setters and one bulk setter, but I could not figure out how to select an existing object based on the event data.
Ideally, the flow would be:
Receive event with targeted object data.
Select the existing object in the canvas.
Perform bulk update.
Refresh canvas.
Hopefully someone with Fabric.JS experience can help me figure this out. Thanks!
UPDATED ANSWER - Thanks AJM!
AJM was correct in suggesting a unique ID for every newly created element. I was also able to create a new ID for all newly created drawing paths as well. Here's how it worked:
var t = new fabric.IText('Edit me...', {
left: $scope.width/2-100,
top: $scope.height/2-50
});
t.set('id',randomHash());
$scope.canvas.add(t);
I also captured newly created paths and added an id:
$scope.canvas.on('path:created',function(e) {
if (e.target.id === undefined) {
e.target.set('id',randomHash());
}
});
However, I encountered an issue where my ID was visible in console log, but it was not present after executing object.toJSON(). This is because Fabric has its own serialization method which trims down the data to a standardized list of properties. To include additional properties, I had to serialize the data for transport like so:
$scope.canvas.on('object:modified',function(e) {
Socket.whiteboardMessage({
object:e.target.toJSON(['id']) // includes "id" in output.
})
});
Now each object has a unique ID with which to perform updates. On the receiver's side of my code, I added AJM's object-lookup function. I placed this code in the "startup" section of my application so it would only run once (after Fabric.js is loaded, of course!)
fabric.Canvas.prototype.getObjectById = function (id) {
var objs = this.getObjects();
for (var i = 0, len = objs.length; i < len; i++) {
if (objs[i].id == id) {
return objs[i];
}
}
return 0;
};
Now, whenever a new socket.io message is received with whiteboard data, I am able to find it in the canvas via this line:
var obj = $scope.canvas.getObjectById(e.object.id);
Inserting and removing are easy, but for updating, this final piece of code did the trick:
obj.set(e.object); // Updates properties
$scope.canvas.renderAll(); // Redraws canvas
$scope.canvas.calcOffset(); // Updates offsets
All of this required me to handle the following events. Paths are treated as objects once they're created.
$scope.canvas.on('object:added',function(e) { });
$scope.canvas.on('object:modified',function(e) { });
$scope.canvas.on('object:moving',function(e) { });
$scope.canvas.on('object:removed',function(e) { });
$scope.canvas.on('path:created',function(e) { });
I did something similar involving a single shared canvas between multiple users and ran into this exact issue.
To solve this problem, I added unique IDs (using a javascript UUID generator) to each object added to the canvas (in my case, there could be many users working on a canvas at a time, thus I needed to avoid collisions; in your case, something simpler could work).
Fabric objects' set method will let you add an arbitrary property, like an id: o.set('id', yourid). Before you add() a new Fabric object to your canvas (and send that across the wire), tack on an ID property. Now, you'll have a unique key by which you can pick out individual objects.
From there, you'd need a method to retrieve an object by ID. Here's what I used:
fabric.Canvas.prototype.getObjectById = function (id) {
var objs = this.getObjects();
for (var i = 0, len = objs.length; i < len; i++) {
if (objs[i].id == id) {
return objs[i];
}
}
return null;
};
When you receive data from your socket, grab that object from the canvas by ID and mutate it using the appropriate set methods or copying properties wholesale (or, if getObjectById returns null, create it).

How to save and retrieve lists in PhoneApplicationService.Current.State?

I need to store and retrieve lists in PhoneApplicationService.Current.State[] but this is not a list of strings or integers:
public class searchResults
{
public string title { get; set; }
public string description { get; set; }
}
public List<searchResults> resultData = new List<searchResults>()
{
//
};
The values of the result are fetched from internet and when the application is switched this data needs to be saved in isolated storage for multitasking. How do I save this list and retrieve it again?
If the question really is about how to save the data then you just do
PhoneApplicationService.Current.State["SearchResultList"] = resultData;
and to retrieve again you do
List<searchResults> loadedResultData = (List<searchResults>)PhoneApplicationService.Current.State["SearchResultList"];
Here is a complete working sample:
// your list for results
List<searchResults> resultData = new List<searchResults>();
// add some example data to save
resultData.Add(new searchResults() { description = "A description", title = "A title" });
resultData.Add(new searchResults() { description = "Another description", title = "Another title" });
// save list of search results to app state
PhoneApplicationService.Current.State["SearchResultList"] = resultData;
// --------------------->
// your app could now be tombstoned
// <---------------------
// load from app state
List<searchResults> loadedResultData = (List<searchResults>)PhoneApplicationService.Current.State["SearchResultList"];
// check if loading from app state succeeded
foreach (searchResults result in loadedResultData)
{
System.Diagnostics.Debug.WriteLine(result.title);
}
(This might stop working when your data structure gets more complex or contains certain types.)
Sounds like you just want to employ standard serialisation for your list object, see here in the MSDN docs
http://msdn.microsoft.com/en-us/library/ms973893.aspx
Or also XML serialisation if you want something that can be edited outside of the application (you can also use the Isolated Storage exploter to grab the file off and edit later)
http://msdn.microsoft.com/en-us/library/182eeyhh(v=vs.71).aspx
Alternatively i would also suggest trying out the Tombstone Helper project by Matt Lacey which can simplify this for you greatly
http://tombstonehelper.codeplex.com/
The answer by Heinrich already summarizes the main idea here - you can use the PhoneApplicationService.State with Lists like with any objects. Check out the MSDN docs on preserving application state: How to: Preserve and Restore Application State for Windows Phone. There's one important point to notice there:
Any data that you store in the State dictionary must be serializable,
either directly or by using data contracts.
Directly here means that the classes are marked as [Serializable]. Regarding your List<searchResults>, it is serializable if searchResults is serializable. To do this, either searchResults and all types referenced by it must be marked with the [Serializable] OR it must be a suitable Data Contract, see Using Data Contracts and Serializable Types. In short, make sure the class is declared as public and that it has a public, parameterless constructor.

Resources