Skip to content

Added logo upload feature #81

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
CI: true
- run: sed -i 's|""|"https://unpkg.com/@workadventure/scripting-api-extra@${{ env.release_tag }}/dist"|g' src/Features/default_assets_url.ts
- run: sed -i 's|http://workadventure.localhost|https://workadventu.re|g' src/Features/default_api_url.ts
- run: npm run build --if-present
- run: npm run test
- run: curl -s https://codecov.io/bash | bash
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/logo_upload_configuration_tile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/logo_upload_demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/logo_upload_layers.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/logo_upload_objects.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/logo_upload_variable_object.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/logo_upload_variable_properties.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
111 changes: 111 additions & 0 deletions docs/logo-upload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
{.section-title.accent.text-primary}
# Logo upload

{.alert.alert-info}
**Important!** To use the "logo upload" feature, you need to [import the "Scripting API Extra" script in your map](about.md#importing-the-extended-features)

The logo upload feature let you customize your maps in a simple way.
By defining some objects and properties in Tiled you can have a logo appearing in your map. Combined with the local configuration feature,
you will have the possibility to change your logo at any time just be heading in front of him and by uploading your brand-new logo file!

To achieve this you will need to define 1 layer, 1 object layer and 2 objects in Tiled.

<figure class="figure">
<img class="figure-img img-fluid rounded" src="images/logo_upload_demo.gif" alt="" />
<figcaption class="figure-caption">Logo upload demo</figcaption>
</figure>

Let's see how this can be done!

## The variable

{.alert.alert-info}
You can read more about variables [here](https://workadventu.re/map-building/variables.md).

First things first, let's create the variable that will hold the logo URL of the uploaded file.
Fo this to happen, you need first to upload an image, right?

That's why when creating the variable you will pass a `type: "upload"` to its properties, this will have the effect of displaying an upload button in the configuration panel.

For the upload input to appear in the configuration panel you MUST move this variable object inside an object layer called `configuration`.

<figure class="figure">
<img class="figure-img img-fluid rounded" src="images/logo_upload_variable_object.png" alt="" />
<figcaption class="figure-caption">Variable object</figcaption>
</figure>

<figure class="figure">
<img class="figure-img img-fluid rounded" src="images/logo_upload_variable_properties.png" alt="" />
<figcaption class="figure-caption">Variable properties</figcaption>
</figure>

As you can see, besides defining the new `upload` type we can also pass optional properties that will restrict the width and height of you image.
Specify the number of pixels in width as `imageWidth` value and the number of pixels in height as `imageHeight` value.

## The embedded website

{.alert.alert-info}
You can read more about embedded websites [here](https://workadventu.re/map-building/website-in-map.md).

<figure class="figure">
<img class="figure-img img-fluid rounded" src="images/logo_upload_embedded_website_object.png" alt="" />
<figcaption class="figure-caption">Embedded website object</figcaption>
</figure>

Create a rectangle object outside the `configuration` object layer (in `floorLayer` for example), this will be out iFrame zone that will be in charge of displaying the actual image.
You have to set its properties to allow the iFrame to interact with external programs (`allowApi: true`) and set the `url` to use the value of the previously defined variable.

<figure class="figure">
<img class="figure-img img-fluid rounded" src="images/logo_upload_embedded_website_properties.png" alt="" />
<figcaption class="figure-caption">Embedded website properties</figcaption>
</figure>

Now, what we are trying to say with this mustache syntax *(it complicates things a bit, but it's actually simple)* is "If there is a URL inside the variable 'logoUrl', pick this URL, otherwise just display another default image": `{{#logoUrl}}{{{logoUrl}}}{{/logoUrl}}{{^logoUrl}}https://workadventu.re/favicon-32x32.png{{/logoUrl}}`.

## The configuration layer

{.alert.alert-info}
You can read more about the local configuration layer [here](https://workadventu.re/map-building-extra/automatic-configuration.md#local-configuration-panel).

We have the variable object that will hold the image URL, we have the zone to display it, now the final step is to define a zone to be able to upload some new images by configuring the variable value.

We just draw a single tile in front of the logo in a dedicated layer. In its properties we specify the name of the variable to configure with `openConfig: logoUrl`.

<figure class="figure">
<img class="figure-img img-fluid rounded" src="images/logo_upload_configuration_tile.png" alt="" />
<figcaption class="figure-caption">Configuration tile</figcaption>
</figure>

{.alert.alert-info}
The rest of the properties are not mandatory. You can restrict who can access to this configuration as well.

<figure class="figure">
<img class="figure-img img-fluid rounded" src="images/logo_upload_configuration_properties.png" alt="" />
<figcaption class="figure-caption">Configuration properties</figcaption>
</figure>

## Let's recap

To recap, we created 2 objects. One must be in the `configuration` object layer in order to be configurable.

<figure class="figure">
<img class="figure-img img-fluid rounded" src="images/logo_upload_objects.png" alt="" />
<figcaption class="figure-caption">Map objects</figcaption>
</figure>

Then, apart from the layers we used to create for a map, we created a layer that will have one tile. This will be the zone in which you can walk to open the local configuration panel. We named this layer 'logoConfiguration'.

<figure class="figure">
<img class="figure-img img-fluid rounded" src="images/logo_upload_layers.png" alt="" />
<figcaption class="figure-caption">Map layers</figcaption>
</figure>

That's it! This feature will be integrated with all of our preset maps.

<figure class="figure">
<img class="figure-img img-fluid rounded" src="images/logo_upload_demo.gif" alt="" />
<figcaption class="figure-caption">Logo upload demo</figcaption>
</figure>

If you want to integrate it yourself into your awesome custom map, now you can!
You can find the full code of the map that tests the logo upload feature [here](https://github.com/workadventure/scripting-api-extra/blob/main/test/maps/configuration_logo.json).
5 changes: 5 additions & 0 deletions docs/menu.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@
'url' => '/map-building-extra/automatic-configuration.md',
'markdown' => 'scripting_api_extra_doc.automatic-configuration',
'editUrl' => 'https://github.com/workadventure/scripting-api-extra/edit/main/docs/automatic-configuration.md',
],[
'title' => 'Logo upload',
'url' => '/map-building-extra/logo-upload.md',
'markdown' => 'scripting_api_extra_doc.logo-upload',
'editUrl' => 'https://github.com/workadventure/scripting-api-extra/edit/main/docs/logo-upload.md',
],[
'title' => 'Properties reference',
'url' => '/map-building-extra/reference.md',
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"@types/mini-css-extract-plugin": "^2.2.0",
"@types/mustache": "^4.1.2",
"@types/webpack-dev-server": "^4.1.0",
"@workadventure/iframe-api-typings": "^1.4.14",
"@workadventure/iframe-api-typings": "^1.6.4",
"copy-webpack-plugin": "^9.0.1",
"cross-env": "^7.0.3",
"css-loader": "^5.2.4",
Expand Down
13 changes: 4 additions & 9 deletions src/Features/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,15 @@ export async function initConfiguration(assetsUrl?: string | undefined): Promise
const properties = new Properties(layer.properties);
const openConfigVariables = properties.getString("openConfig");
if (openConfigVariables && layer.type === "tilelayer") {
initLocalConfigurationPanel(openConfigVariables, properties);
initLocalConfigurationPanel(layer.name, openConfigVariables, properties);
}
}
}
}

function initLocalConfigurationPanel(openConfigVariables: string, properties: Properties): void {
function initLocalConfigurationPanel(layerName: string, openConfigVariables: string, properties: Properties): void {
let actionMessage: ActionMessage | undefined = undefined;

const zoneName = properties.getString("zone");
if (!zoneName) {
throw new Error('Missing "zone" property');
}

const tag = properties.getString("openConfigAdminTag");
let allowedByTag = true;
if (tag && !WA.player.tags.includes(tag)) {
Expand All @@ -67,7 +62,7 @@ function initLocalConfigurationPanel(openConfigVariables: string, properties: Pr
WA.nav.closeCoWebSite();
}

WA.room.onEnterZone(zoneName, () => {
WA.room.onEnterLayer(layerName).subscribe(() => {
const openConfigTriggerValue = properties.getString("openConfigTrigger");

// Do not display conf panel if the user is not allowed by tag
Expand All @@ -80,7 +75,7 @@ function initLocalConfigurationPanel(openConfigVariables: string, properties: Pr
}
});

WA.room.onLeaveZone(zoneName, () => {
WA.room.onLeaveLayer(layerName).subscribe(() => {
if (actionMessage) {
actionMessage.remove();
closeConfigurationPanel();
Expand Down
5 changes: 5 additions & 0 deletions src/Features/default_api_url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* The base URL for the default API.
* This file is updated dynamically during the package build process.
*/
export const defaultApiUrl = "http://workadventure.localhost/api";
42 changes: 38 additions & 4 deletions src/Features/properties_templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,51 @@ export async function initPropertiesTemplates(): Promise<void> {
) {
continue;
}

const template = new TemplateValue(property.value, WA.state);
if (template.isPureString()) {
continue;
}
const newValue = template.getValue();
setProperty(layerName, property.name, newValue);
setLayerProperty(layerName, property.name, newValue);

template.onChange((newValue) => {
setProperty(layerName, property.name, newValue);
setLayerProperty(layerName, property.name, newValue);
});
}

// Parse the URL of the integrated websites (for example if mustache is used)
// Here we want to select the Tiled object layers with the type 'website' and the property 'url'
const promises = [];
if (layer.type === "objectgroup") {
for (const object of layer.objects) {
if (object.type === "website") {
for (const property of object.properties) {
if (property.name === "url") {
const template = new TemplateValue(property.value, WA.state);
if (template.isPureString()) {
continue;
}
const newValue = template.getValue();
promises.push(setWebsiteProperty(object.name, newValue));

template.onChange((newValue) => {
setWebsiteProperty(object.name, newValue);
});
}
}
}
}
}
await Promise.all(promises);
}
}

/**
* Sets the property value on the map.
* Sets the property value of a layer on the map.
* Furthermore, if the property name is "visible", modify the visibility of the layer.
*/
function setProperty(layerName: string, propertyName: string, value: string): void {
function setLayerProperty(layerName: string, propertyName: string, value: string): void {
WA.room.setProperty(layerName, propertyName, value);
if (propertyName === "visible") {
if (value) {
Expand All @@ -43,3 +69,11 @@ function setProperty(layerName: string, propertyName: string, value: string): vo
}
}
}

/**
* Sets the property value of an object of type 'website' on the map.
*/
async function setWebsiteProperty(objectName: string, value: string): Promise<void> {
const website = await WA.room.website.get(objectName);
website.url = value;
}
71 changes: 70 additions & 1 deletion src/Iframes/Configuration/Components/Field.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<script lang="ts">
import type {VariableDescriptor} from "../../../VariablesExtra";
import {createStoreFromVariable} from "../../../VariableMapper";
import {prepareUpload, uploadFile} from "../../../Uploader";
import type {Writable} from "svelte/store";
import {formStore} from "../Stores/form";

export let variable: VariableDescriptor;

Expand All @@ -14,13 +16,24 @@
const stringVariableStore = variableStore as Writable<string>;
const boolVariableStore = variableStore as Writable<boolean>;

let container: HTMLElement;
let fileInput: HTMLInputElement;

function getAllowedValues() {
const allowedValuesStr = variable.properties.mustGetString('allowed_values');
return JSON.parse(allowedValuesStr) as {[key: string]: string | number | undefined};
}

function onChange(event: Event) {
$variableStore = (event.target as HTMLInputElement).value;
if (type === 'upload') {
prepareUpload(event, variable)
} else {
$variableStore = (event.target as HTMLInputElement).value;
}
}

async function onUpload() {
$variableStore = await uploadFile()
}
</script>

Expand Down Expand Up @@ -49,6 +62,30 @@
</label>
{/each}
</div>
{:else if type === 'upload' }
<div class="nes-field field upload">
<span>{label}</span>
<div class="field">
<input type="file" accept="image/*"
name={variable.name}
id="upload_{variable.name}"
bind:this={fileInput}
on:change={onChange}
class="nes-btn">

<div>
<div bind:this={container} class="image-preview">
{#if $formStore.showImage}
<img bind:this={$formStore.image} src="" alt="Preview" />
{:else}
<span>/</span>
{/if}
</div>

<button class="nes-btn is-primary upload-btn" on:click={onUpload}>Upload & Replace</button>
</div>
</div>
</div>
{:else}
<div class="nes-field field">
<label for="input_{variable.name}">{label}</label>
Expand All @@ -59,6 +96,9 @@
<div class="description">{ description }</div>
{/if}

{#if $formStore.error }
<div class="error"><p>{ $formStore.error }</p></div>
{/if}

<style lang="scss">
.field {
Expand All @@ -70,4 +110,33 @@
color: #777;
margin-bottom: 30px;
}

.upload {
height: 128px;

.image-preview {
image-rendering: -webkit-crisp-edges;
image-rendering: crisp-edges;
width: 256px;
min-height: 128px;
border: 2px solid #ddd;
margin-top: 15px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
color: #ccc;
}

.upload-btn {
display: flex;
margin-top: 20px;
}
}

.error {
margin-top: 25px;
color: #cb2525;
display: inline-block;
}
</style>
Loading