Compare commits

..

36 Commits

Author SHA1 Message Date
Sabe Jones
e4d006e5cd 3.103.0 2017-07-16 16:25:07 +00:00
Sabe Jones
801b53857f fix(payments): short circuit for non-gift txns (#8881) 2017-07-16 09:24:17 -07:00
Keith Holliday
f8571ec5d5 Moved notifications to be read after yesterdailies (#8872)
* Moved notifications to be read after yesterdailies

* Prevent when user needs cron

* Updated tests
2017-07-16 09:24:09 -07:00
Matteo Pagliazzi
78ba596504 Groups can prevent members from getting gems (#8870)
* add possibility for group to block members from getting gems

* fixes

* fix tests

* adds some tests

* unit tests

* finish unit tests

* remove old code
2017-07-16 09:23:57 -07:00
Sabe Jones
fe9521a63f 3.102.3 2017-07-14 12:11:08 +00:00
Sabe Jones
c4348d8e47 Merge branch 'release' into develop 2017-07-13 22:29:35 +00:00
Sabe Jones
9b0ee6a726 3.102.2 2017-07-13 22:29:03 +00:00
Sabe Jones
c23fad3077 chore(i18n): update locales 2017-07-13 22:28:23 +00:00
Sabe Jones
6850a1fcb4 chore(news): Blog Bailey 2017-07-13 22:23:35 +00:00
Keith Holliday
bc477455bb Tasks cron ryamodal fixes (#8871)
* Changed assumption of timezone location

* Added checks for RYA and moved cron check

* Fixed modal scope issue
2017-07-13 15:11:27 -07:00
Sabe Jones
88c56c9877 Merge branch 'release' into develop 2017-07-12 20:42:23 +00:00
Sabe Jones
a4ee3aaa7e Merge branch 'release' into develop 2017-07-11 21:48:18 +00:00
Keith Holliday
052c653cd3 Added initial inbox (#8852)
* Added initial inbox

* Minor styles

* Added more styles, selected conversations, sending messages

* Added translations and colors
2017-07-10 21:09:31 -06:00
Keith Holliday
3aa7b4b631 Fixed or logic break (#8857) 2017-07-10 21:08:35 -06:00
Sabe Jones
045378b820 Merge branch 'release' into develop 2017-07-10 20:20:24 +00:00
negue
dd29c60d87 [WIP] drag/drop fixes (#8851)
* Stable: Highlight pet on dragging food / add drag* events

* Stable: hatch dialog instead of popover / .btn-flat / update bootstrap-vie

* Layout: change sidebar width to fixed 236px - removed .col-2/.col-10

* Stable: Drag&Drop Food Image + Text - refactor directive to use custom event names

* Stable: fixes

* Stable: click to select food + attached food info box

* fix lint issues

* Drag&Drop&Click: Potions on Eggs - fix click on item + attached infobox-position in click mode
2017-07-10 10:07:23 +02:00
Sabe Jones
78fd79931e Merge branch 'release' into develop 2017-07-08 01:49:25 +00:00
Keith Holliday
19ba1290f6 Added new web profile for checkout (#8699)
* Added new web profile for checkout

* Fixed es6 syntax

* Fixed config path
2017-07-06 15:16:54 -07:00
Grayson Gilmore
c509c8e04f Test /api/v3/tasks/unlink-all and unlink-one (#8810)
* Add tests for POST /tasks/unlink-all/:challengeId

* Add test for keep=keep-all query

* Cleanup

* Setup

* Cleanup

* Add tests for unlink-one
2017-07-06 14:25:31 -07:00
Todd Medema
00d4393024 reduce top margins on task filter bar to reduce dead space below 'add multiple' link / make UI more space efficient (#8806) 2017-07-06 14:13:35 -07:00
yugensoft
c502b1997b Updated new-groups / group-plans static page #8674 (#8729)
* dummy

* Renamed internationalized strings to more meaningful names

* moved the new group creation state out to its own URL, so it can also be linked to by the static/plans page

* Added redirect-through-login functionality from the static/plans page new-group button
This includes a static non-modal login page (similar to how other sites have both a login page and login modal)
The login body has been abstracted out from its modal-specific view into mixins to accomplish this

* deleted bak files added by mistake

* deleted scripts added by mistake

* changed static/plans Create Group button text

* Added form link (https://github.com/HabitRPG/habitica/issues/8674#issuecomment-303518039)
Removed changes to non-EN locale files (https://github.com/HabitRPG/habitica/pull/8729#issuecomment-303555211)

* reverted key name changes as per https://github.com/HabitRPG/habitica/pull/8729#issuecomment-304515534

* changed $rootScope to $scope
https://github.com/HabitRPG/habitica/pull/8729#discussion_r120695874
2017-07-06 14:08:02 -07:00
madpink
4435862ff2 Updating User API Doc (part 4) (#8792)
* Updating User API Doc (part 4)

* Fixed trailing space
2017-07-06 13:48:00 -07:00
Keith Holliday
e901850a6f continuation of PR #8161 Display error notification when attempting to purchase invalid amount of gems - fixes #8145 (#8688)
* Translation string for error notification

* Use function instead of a link for paypal

* Inject notification service, function to check the amount of gems to purchase, function to handle payments with paypal

* Throw error if amount of gems is zero or negative

* Add condition to raise error if amount is negative

* Added gem errors for gifts 0 or less

* Fixed linting and broken test

* Fixed test syntax

* Added back needed strings

* Fixed group locales
2017-07-06 13:43:43 -07:00
LlamaLad
48bbc22fb4 Dailies new subheading and summary change (#8799)
* Dailies new subheading and summary change

* summary change
2017-07-06 13:31:13 -07:00
Alys
c1e5d8b573 add missing string for mountNowOwned error message (#8843) 2017-07-06 13:23:20 -07:00
Andrew Schultz
6951b79b95 two typos in questsContent (#8837) 2017-07-06 13:20:34 -07:00
Keith Holliday
1c3a12f37d [WIP] Added initial tavern updates (#8845)
* Added initial tavern updates

* Translations and styles

* Fixed locales and lint
2017-07-06 09:38:52 -06:00
Keith Holliday
3c71748a1b Renamed production docker file (#8850) 2017-07-05 13:29:25 -07:00
Sabe Jones
8e56222fc7 Merge branch 'release' into develop 2017-07-05 20:11:13 +00:00
Keith Holliday
6bc6c09c75 New client creator (#8841)
* Added initial creator

* Added initial styles and functionality for creator

* More style changes

* Translations and styles

* More active classes

* Removed extra locales
2017-07-04 13:11:08 -05:00
Sabe Jones
0cbf9354cc Merge branch 'release' into develop 2017-07-03 23:05:52 +00:00
Joe P
06c53677f6 Group Approvals Formatting - Fix #8677 (#8784)
* Add task indication to Group Plans Tasks Awaiting Approval page

* Render markdown tasks on Group Plans Tasks Awaiting Approval page

* Fix panel code- fixes formatting issue of Approve button on Group Plans Tasks Awaiting Approval page
2017-07-01 09:33:24 -07:00
Sabe Jones
aa91c5dbae Kubernetes support for dev environments (#8753)
* Run Habitica in Kubernetes

* fix(docs): Address PR comments
2017-07-01 09:30:08 -07:00
Keith Holliday
5b7c7b77c8 Added docker production file (#8846)
* Added docker production file

* Added tag clone

* Removed docker prod
2017-07-01 09:28:51 -07:00
Vicente
d3d221dccb Repeat monthly fix https://github.com/HabitRPG/habitica/issues/8831 (#8835)
* checking nexDueDate if it's an array before applying .map

* used array created

* change let for 'strict mode'

* Used ajax when canceling from the website (#8697)

* Used ajax when canceling from the website

* Fixed grammar issue

* Payments gem reset (#8712)

* Added gem reset if user does not have date last updated set

* Fixed login of adding updated date

* fix bug that prevented sending of emails to admin addresses (#8832)

* feat(content): June 2017 subscriber items

* chore(sprites): compile

* chore(i18n): update locales

* 3.98.0

* [WIP] client/inventory/stable (#8737)

* Stable: basic layout (based on equipment)

* extract item popover-content as component

# Conflicts:
#	website/client/components/inventory/item.vue

* extract item-content as slot - list standard pets with image

* dynamic list petGroups in sidebar / content - with selected / open filter

* itemContentClass for pets

* fix filter - extract filter labels

* Filter: Hide Missing

* fix position of pets

* sort by: A-Z, Color, Hatchable

* refactor animalList - created the list once per type and cache it - sort now works before viewing one or all pets

* custom petItem to show the progress

* list specialPets - rename petGroup to animalGroup (more generic)

* equip a pet

* filter animals by input

* count pets

* list mounts

* hatchable pet popover

* hatchable pet popover #2

* PixelPaw Opacity for not owned and not hatchable pets - change item background for unowned pets

* Hold to hatch the pet - cleanup

* add food drawer + countBadge - special mounts - hide un-owned special animals - fixes

* Sliding Drawer: Buttons to scroll left/right

* Drag&Drop: food on pets

* fix hold to hatch - use mouseleave event

* 'Show All' / 'Show Less' - Animals

* Matts Image + Popover + use image width as sidebar width (removed col-2 / col-10)

* fixes: colors, v-once, drawer-errorMessage, strings

* drawerSlider - split items to pages / add divider / add first item of the next page - ResizeDirective

* ResizeDirective - throttle emits by `v-resize="value"` - fix drawer left padding

* show animals by available content width

* change sortBy button / label

* fix pet colors / backgrounds

* DragDropDirective - grabbing cursor

* remove browser specific prefixes

* fix lint issues

* show welcome dialog

* change translationkey (noFood, already exists)

* used array created

* change let for 'strict mode'
2017-07-01 09:25:04 -07:00
negue
53c610236b Client/stable fixes - part 1 (#8840)
* fixes: show ten pets in a row - show pet / no feeding progress if mount already owned

* fixes: disabled filter instead of hiding them

* fixes: don't hide special pets - same item style for mounts

* fixes: avatar changes of pet / mount

* fixes: unfocus first dropdown-item by css

* fixes: unfocus first dropdown-item by css (remove :focus style)- added "What does my pet like to eat?" popover
2017-07-01 17:46:24 +02:00
129 changed files with 3387 additions and 813 deletions

View File

@@ -1,18 +1,18 @@
FROM node:boron
# Install global packages
RUN npm install -g gulp grunt-cli bower mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN npm install
RUN bower install --allow-root
# Create Build dir
RUN mkdir -p ./website/build
# Start Habitica
EXPOSE 3000
CMD ["npm", "start"]
FROM node:boron
# Install global packages
RUN npm install -g gulp grunt-cli bower mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN npm install
RUN bower install --allow-root
# Create Build dir
RUN mkdir -p ./website/build
# Start Habitica
EXPOSE 3000
CMD ["npm", "start"]

View File

@@ -6,13 +6,14 @@ RUN npm install -g gulp grunt-cli bower mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone --branch v3.99.0 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN git clone --branch v3.102.2 https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN npm install
RUN bower install --allow-root
RUN gulp build:prod --force
# Create Build dir
RUN mkdir -p ./website/build
# Start Habitica
EXPOSE 3000
CMD ["npm", "start"]
CMD ["node", "./website/transpiled-babel/index.js"]

View File

@@ -66,7 +66,8 @@
},
"mode":"sandbox",
"client_id":"client_id",
"client_secret":"client_secret"
"client_secret":"client_secret",
"experience_profile_id": ""
},
"IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/",
"LOGGLY_TOKEN": "token",

60
kubernetes/README.md Normal file
View File

@@ -0,0 +1,60 @@
# Habitica in Kubernetes
This is a set of sample Kubernetes configuration files to launch Habitica under AWS, both as a single-node web frontend as well as a multi-node web frontend.
## Prerequisites
* An AWS account.
* A working Kubernetes installation.
* A basic understanding of how to use Kubernetes. https://kubernetes.io/
* A persistent volume for MongoDB data.
* Docker images of Habitica.
+ You can use your own, or use the one included in the YAML files.
+ If you use your own, you'll need a fork of the Habitica GitHub repo and your own Docker Hub repo, both of which are free.
## Before you begin
1. Set up Kubernetes.
2. Create an EBS volume for MongoDB data.
+ Make a note of the name, you'll need it later.
## Starting MongoDB
1. Edit mongo.yaml
+ Find the volumeID line.
+ Change the volume to the one created in the section above.
2. Run the following commands:
+ `kubectl.sh create -f mongo.yaml`
+ `kubectl.sh create -f mongo-service.yaml`
3. Wait for the MongoDB pod to start up.
## Starting a Single Web Frontend
1. Run the following commands:
+ `kubectl.sh create -f habitica.yaml`
+ `kubectl.sh create -f habitica-service.yaml`
2. Wait for the frontend to start up.
## Starting Multi-node Web Frontend
1. Run the following commands :
+ `kubectl.sh create -f habitica-rc.yaml`
+ `kubectl.sh create -f habitica-service.yaml`
2. Wait for the frontend to start up.
## Accessing Your Habitica web interface
Using `kubectl describe svc habiticaweb` get the hostname generated for the Habitica service. Open a browser and go to http://hostname:3000 to access the web front-end for the installations above.
## Shutting down
Shutting down is basically done by reversing the steps above:
+ `kubectl.sh delete -f habitica-service.yaml`
+ `kubectl.sh delete -f habitica.yaml (or habitica-rc.yaml)`
+ `kubectl.sh delete -f mongo-service.yaml`
+ `kubectl.sh delete -f mongo.yaml`
You can also just shut down all of Kubernetes as well.
## Notes
+ MongoDB data will be persistent! If you need to start with a fresh database, you'll need to remove the volume and re-create it.
+ On AWS, you probably want to use at least t2.medium minion nodes for Kubernetes. The default t2.small is too small for more than two Habitica nodes.
## Future Plans
+ Multi-node MongoDB.
+ Monitoring
+ Instructions for a better hostname. The default generated ones stink.
+ More to come....

View File

@@ -0,0 +1,25 @@
apiVersion: v1
kind: ReplicationController
metadata:
name: habitica
labels:
name: habitica
spec:
replicas: 4
selector:
name: habitica
template:
metadata:
labels:
name: habitica
spec:
containers:
- name: habitica
image: ksonney/habitrpg:latest
env:
- name: NODE_DB_URI
value: mongodb://mongosvc/habitrpg
ports:
- containerPort: 3000
hostPort: 3000
name: habitica

View File

@@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
labels:
name: habiticaweb
name: habiticaweb
spec:
ports:
# the port that this service should serve on
- port: 3000
# label keys and values that must match in order to receive traffic for this service
selector:
name: habitica
type: LoadBalancer

22
kubernetes/habitica.yaml Normal file
View File

@@ -0,0 +1,22 @@
apiVersion: v1
kind: Pod
metadata:
name: habitica
labels:
name: habitica
spec:
containers:
# - image: mongo:latest
# name: mongo
# ports:
# - containerPort: 27017
# name: mongo
- image: ksonney/habitrpg:latest
name: habitica
env:
- name: NODE_DB_URI
value: mongodb://mongosvc/habitrpg
ports:
- containerPort: 3000
hostPort: 3000
name: habitica

View File

@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
labels:
name: mongosvc
name: mongosvc
spec:
ports:
# the port that this service should serve on
- port: 27017
# label keys and values that must match in order to receive traffic for this service
selector:
name: mongodb

28
kubernetes/mongo.yaml Normal file
View File

@@ -0,0 +1,28 @@
apiVersion: v1
kind: Pod
metadata:
name: mongodb
labels:
name: mongodb
spec:
containers:
- resources:
limits :
cpu: 0.5
image: mongo
name: mongodb
ports:
- containerPort: 27017
hostPort: 27017
name: mongo
volumeMounts:
# # name must match the volume name below
- name: mongo-persistent-storage
# # mount path within the container
mountPath: /data/db
volumes:
- name: mongo-persistent-storage
awsElasticBlockStore:
volumeID: aws://YOUR-REGION/YOUR-VOLNAME
fsType: ext3

View File

@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "3.102.1",
"version": "3.103.0",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "^3.8.1",
@@ -31,7 +31,7 @@
"bluebird": "^3.3.5",
"body-parser": "^1.15.0",
"bootstrap": "^4.0.0-alpha.6",
"bootstrap-vue": "^0.15.8",
"bootstrap-vue": "^0.16.1",
"bower": "~1.3.12",
"browserify": "~12.0.1",
"compression": "^1.6.1",

View File

@@ -1,3 +1,5 @@
require("babel-register");
require("babel-polyfill");
// This file is used for creating paypal billing plans. PayPal doesn't have a web interface for setting up recurring
// payment plan definitions, instead you have to create it via their REST SDK and keep it updated the same way. So this
// file will be used once for initing your billing plan (then you get the resultant plan.id to store in config.json),
@@ -7,10 +9,10 @@ var path = require('path');
var nconf = require('nconf');
var _ = require('lodash');
var paypal = require('paypal-rest-sdk');
var blocks = require('../../../../common').content.subscriptionBlocks;
var blocks = require('../website/common').content.subscriptionBlocks;
var live = nconf.get('PAYPAL:mode')=='live';
nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../../../config.json')));
nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../config.json')));
var OP = 'create'; // list create update remove
@@ -49,6 +51,8 @@ _.each(blocks, function(block){
});
})
// @TODO: Add cli library for this
switch(OP) {
case "list":
paypal.billingPlan.list({status: 'ACTIVE'}, function(err, plans){
@@ -91,4 +95,17 @@ switch(OP) {
});
break;
case "remove": break;
case 'create-webprofile':
let webexpinfo = {
"name": "HabiticaProfile",
"input_fields": {
"no_shipping": 1,
},
};
paypal.webProfile.create(webexpinfo, (error, result) => {
console.log(error, result)
})
break;
}

View File

@@ -114,6 +114,26 @@ describe('POST /challenges/:challengeId/winner/:winnerId', () => {
await expect(winningUser.sync()).to.eventually.have.property('balance', oldBalance + challenge.prize / 4);
});
it('doesn\'t gives winner gems if group policy prevents it', async () => {
let oldBalance = winningUser.balance;
let oldLeaderBalance = (await groupLeader.sync()).balance;
await winningUser.update({
'purchased.plan.customerId': 'group-plan',
});
await group.update({
'leaderOnly.getGems': true,
'purchased.plan.customerId': 123,
});
await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`);
await sleep(0.5);
await expect(winningUser.sync()).to.eventually.have.property('balance', oldBalance);
await expect(groupLeader.sync()).to.eventually.have.property('balance', oldLeaderBalance + challenge.prize / 4);
});
it('doesn\'t refund gems to group leader', async () => {
let oldBalance = (await groupLeader.sync()).balance;

View File

@@ -0,0 +1,109 @@
import {
generateUser,
generateGroup,
generateChallenge,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('POST /tasks/unlink-all/:challengeId', () => {
let user;
let guild;
let challenge;
let tasksToTest = {
habit: {
text: 'test habit',
type: 'habit',
up: false,
down: true,
},
todo: {
text: 'test todo',
type: 'todo',
},
daily: {
text: 'test daily',
type: 'daily',
frequency: 'daily',
everyX: 5,
startDate: new Date(),
},
reward: {
text: 'test reward',
type: 'reward',
},
};
beforeEach(async () => {
user = await generateUser();
guild = await generateGroup(user);
challenge = await generateChallenge(user, guild);
});
it('fails if no keep query', async () => {
await expect(user.post(`/tasks/unlink-all/${challenge._id}`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('fails if invalid challenge id', async () => {
await expect(user.post('/tasks/unlink-all/123?keep=remove-all'))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('fails on an unbroken challenge', async () => {
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
await expect(user.post(`/tasks/unlink-all/${challenge._id}?keep=remove-all`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('cantOnlyUnlinkChalTask'),
});
});
it('unlinks all tasks from a challenge and deletes them on keep=remove-all', async () => {
const daily = await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.habit);
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.reward);
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.todo);
await user.del(`/challenges/${challenge._id}`);
const response = await user.post(`/tasks/unlink-all/${challenge._id}?keep=remove-all`);
expect(response).to.eql({});
await expect(user.get(`/tasks/${daily._id}`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('taskNotFound'),
});
});
it('unlinks a task from a challenge on keep=keep-all', async () => {
const daily = await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
const anotherUser = await generateUser();
await user.post(`/groups/${guild._id}/invite`, {
uuids: [anotherUser._id],
});
// Have the second user join the group and challenge
await anotherUser.post(`/groups/${guild._id}/join`);
await anotherUser.post(`/challenges/${challenge._id}/join`);
// Have the leader delete the challenge and unlink the tasks
await user.del(`/challenges/${challenge._id}`);
await user.post(`/tasks/unlink-all/${challenge._id}?keep=keep-all`);
// Get the second task for the second user
const [, anotherUserTask] = await anotherUser.get('/tasks/user');
// Expect the second user to still have the task, but unlinked
expect(anotherUserTask.challenge).to.eql({
taskId: daily._id,
id: challenge._id,
shortName: challenge.shortName,
broken: 'CHALLENGE_DELETED',
winner: null,
});
});
});

View File

@@ -0,0 +1,110 @@
import {
generateUser,
generateGroup,
generateChallenge,
translate as t,
} from '../../../../helpers/api-integration/v3';
import { v4 as generateUUID } from 'uuid';
describe('POST /tasks/unlink-one/:taskId', () => {
let user;
let guild;
let challenge;
let tasksToTest = {
habit: {
text: 'test habit',
type: 'habit',
up: false,
down: true,
},
todo: {
text: 'test todo',
type: 'todo',
},
daily: {
text: 'test daily',
type: 'daily',
frequency: 'daily',
everyX: 5,
startDate: new Date(),
},
reward: {
text: 'test reward',
type: 'reward',
},
};
beforeEach(async () => {
user = await generateUser();
guild = await generateGroup(user);
challenge = await generateChallenge(user, guild);
});
it('fails if no keep query', async () => {
const daily = await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
await expect(user.post(`/tasks/unlink-one/${daily._id}`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('fails if invalid task id', async () => {
await expect(user.post('/tasks/unlink-one/123?keep=remove'))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
});
});
it('fails on task not found', async () => {
await expect(user.post(`/tasks/unlink-one/${generateUUID()}?keep=keep`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('taskNotFound'),
});
});
it('fails on task unlinked to challenge', async () => {
let daily = await user.post('/tasks/user', tasksToTest.daily);
await expect(user.post(`/tasks/unlink-one/${daily._id}?keep=keep`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('cantOnlyUnlinkChalTask'),
});
});
it('fails on unbroken challenge', async () => {
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
let [daily] = await user.get('/tasks/user');
await expect(user.post(`/tasks/unlink-one/${daily._id}?keep=keep`))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('cantOnlyUnlinkChalTask'),
});
});
it('unlinks a task from a challenge and saves it on keep=keep', async () => {
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
let [, daily] = await user.get('/tasks/user');
await user.del(`/challenges/${challenge._id}`);
await user.post(`/tasks/unlink-one/${daily._id}?keep=keep`);
[, daily] = await user.get('/tasks/user');
expect(daily.challenge).to.eql({});
});
it('unlinks a task from a challenge and deletes it on keep=remove', async () => {
await user.post(`/tasks/challenge/${challenge._id}`, tasksToTest.daily);
let [, daily] = await user.get('/tasks/user');
await user.del(`/challenges/${challenge._id}`);
await user.post(`/tasks/unlink-one/${daily._id}?keep=remove`);
const tasks = await user.get('/tasks/user');
// Only the default task should remain
expect(tasks.length).to.eql(1);
});
});

View File

@@ -1,5 +1,6 @@
import {
generateUser,
createAndPopulateGroup,
translate as t,
} from '../../../../helpers/api-integration/v3';
@@ -31,4 +32,70 @@ describe('POST /user/purchase/:type/:key', () => {
expect(user.items[type][key]).to.equal(1);
});
it('can convert gold to gems if subscribed', async () => {
let oldBalance = user.balance;
await user.update({
'purchased.plan.customerId': 'group-plan',
'stats.gp': 1000,
});
await user.post('/user/purchase/gems/gem');
await user.sync();
expect(user.balance).to.equal(oldBalance + 0.25);
});
it('leader can convert gold to gems even if the group plan prevents it', async () => {
let { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'test',
type: 'guild',
privacy: 'private',
},
});
await group.update({
'leaderOnly.getGems': true,
'purchased.plan.customerId': 123,
});
await groupLeader.sync();
let oldBalance = groupLeader.balance;
await groupLeader.update({
'purchased.plan.customerId': 'group-plan',
'stats.gp': 1000,
});
await groupLeader.post('/user/purchase/gems/gem');
await groupLeader.sync();
expect(groupLeader.balance).to.equal(oldBalance + 0.25);
});
it('cannot convert gold to gems if the group plan prevents it', async () => {
let { group, members } = await createAndPopulateGroup({
groupDetails: {
name: 'test',
type: 'guild',
privacy: 'private',
},
members: 1,
});
await group.update({
'leaderOnly.getGems': true,
'purchased.plan.customerId': 123,
});
let oldBalance = members[0].balance;
await members[0].update({
'purchased.plan.customerId': 'group-plan',
'stats.gp': 1000,
});
await expect(members[0].post('/user/purchase/gems/gem'))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('groupPolicyCannotGetGems'),
});
await members[0].sync();
expect(members[0].balance).to.equal(oldBalance);
});
});

View File

@@ -102,6 +102,7 @@ describe('Amazon Payments', () => {
});
it('should purchase gems', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
await amzLib.checkout({user, orderReferenceId, headers});
expect(paymentBuyGemsStub).to.be.calledOnce;
@@ -111,22 +112,52 @@ describe('Amazon Payments', () => {
headers,
});
expectAmazonStubs();
expect(user.canGetGems).to.be.calledOnce;
user.canGetGems.restore();
});
it('should gift gems', async () => {
it('should error if gem amount is too low', async () => {
let receivingUser = new User();
receivingUser.save();
let gift = {
type: 'gems',
gems: {
amount: 16,
amount: 0,
uuid: receivingUser._id,
},
};
await expect(amzLib.checkout({gift, user, orderReferenceId, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 400,
message: 'Amount must be at least 1.',
name: 'BadRequest',
});
});
it('should error if user cannot get gems gems', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
await expect(amzLib.checkout({user, orderReferenceId, headers})).to.eventually.be.rejected.and.to.eql({
httpCode: 401,
message: i18n.t('groupPolicyCannotGetGems'),
name: 'NotAuthorized',
});
user.canGetGems.restore();
});
it('should gift gems', async () => {
let receivingUser = new User();
await receivingUser.save();
let gift = {
type: 'gems',
uuid: receivingUser._id,
gems: {
amount: 16,
},
};
amount = 16 / 4;
await amzLib.checkout({gift, user, orderReferenceId, headers});
gift.member = receivingUser;
expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledWith({
user,

View File

@@ -57,7 +57,20 @@ describe('Apple Payments', () => {
});
});
it('errors if the user cannot purchase gems', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: i18n.t('groupPolicyCannotGetGems'),
});
user.canGetGems.restore();
});
it('purchases gems', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
await applePayments.verifyGemPurchase(user, receipt, headers);
expect(iapSetupStub).to.be.calledOnce;
@@ -74,6 +87,8 @@ describe('Apple Payments', () => {
amount: 5.25,
headers,
});
expect(user.canGetGems).to.be.calledOnce;
user.canGetGems.restore();
});
});

View File

@@ -63,7 +63,21 @@ describe('Google Payments', () => {
});
});
it('should throw an error if user cannot purchase gems', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
message: i18n.t('groupPolicyCannotGetGems'),
});
user.canGetGems.restore();
});
it('purchases gems', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
await googlePayments.verifyGemPurchase(user, receipt, signature, headers);
expect(iapSetupStub).to.be.calledOnce;
@@ -82,6 +96,8 @@ describe('Google Payments', () => {
amount: 5.25,
headers,
});
expect(user.canGetGems).to.be.calledOnce;
user.canGetGems.restore();
});
});

View File

@@ -61,23 +61,54 @@ describe('Paypal Payments', () => {
});
it('creates a link for gem purchases', async () => {
let link = await paypalPayments.checkout();
let link = await paypalPayments.checkout({user: new User()});
expect(paypalPaymentCreateStub).to.be.calledOnce;
expect(paypalPaymentCreateStub).to.be.calledWith(getPaypalCreateOptions('Habitica Gems', 5.00));
expect(link).to.eql(approvalHerf);
});
it('creates a link for gifting gems', async () => {
it('should error if gem amount is too low', async () => {
let receivingUser = new User();
receivingUser.save();
let gift = {
type: 'gems',
gems: {
amount: 16,
amount: 0,
uuid: receivingUser._id,
},
};
await expect(paypalPayments.checkout({gift}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 400,
message: 'Amount must be at least 1.',
name: 'BadRequest',
});
});
it('should error if the user cannot get gems', async () => {
let user = new User();
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
await expect(paypalPayments.checkout({user})).to.eventually.be.rejected.and.to.eql({
httpCode: 401,
message: i18n.t('groupPolicyCannotGetGems'),
name: 'NotAuthorized',
});
});
it('creates a link for gifting gems', async () => {
let receivingUser = new User();
await receivingUser.save();
let gift = {
type: 'gems',
uuid: receivingUser._id,
gems: {
amount: 16,
},
};
let link = await paypalPayments.checkout({gift});
expect(paypalPaymentCreateStub).to.be.calledOnce;

View File

@@ -48,7 +48,57 @@ describe('Stripe Payments', () => {
payments.createSubscription.restore();
});
it('should error if gem amount is too low', async () => {
let receivingUser = new User();
receivingUser.save();
gift = {
type: 'gems',
gems: {
amount: 0,
uuid: receivingUser._id,
},
};
await expect(stripePayments.checkout({
token,
user,
gift,
groupId,
email,
headers,
coupon,
}, stripe))
.to.eventually.be.rejected.and.to.eql({
httpCode: 400,
message: 'Amount must be at least 1.',
name: 'BadRequest',
});
});
it('should error if user cannot get gems', async () => {
gift = undefined;
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
await expect(stripePayments.checkout({
token,
user,
gift,
groupId,
email,
headers,
coupon,
}, stripe)).to.eventually.be.rejected.and.to.eql({
httpCode: 401,
message: i18n.t('groupPolicyCannotGetGems'),
name: 'NotAuthorized',
});
});
it('should purchase gems', async () => {
gift = undefined;
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
await stripePayments.checkout({
token,
user,
@@ -73,16 +123,18 @@ describe('Stripe Payments', () => {
paymentMethod: 'Stripe',
gift,
});
expect(user.canGetGems).to.be.calledOnce;
user.canGetGems.restore();
});
it('should gift gems', async () => {
let receivingUser = new User();
receivingUser.save();
await receivingUser.save();
gift = {
type: 'gems',
uuid: receivingUser._id,
gems: {
amount: 16,
uuid: receivingUser._id,
},
};
@@ -96,7 +148,6 @@ describe('Stripe Payments', () => {
coupon,
}, stripe);
gift.member = receivingUser;
expect(stripeChargeStub).to.be.calledOnce;
expect(stripeChargeStub).to.be.calledWith({
amount: '400',

View File

@@ -1,6 +1,7 @@
import Bluebird from 'bluebird';
import moment from 'moment';
import { model as User } from '../../../../../website/server/models/user';
import { model as Group } from '../../../../../website/server/models/group';
import common from '../../../../../website/common';
describe('User Model', () => {
@@ -179,6 +180,75 @@ describe('User Model', () => {
});
});
context('canGetGems', () => {
let user;
let group;
beforeEach(() => {
user = new User();
let leader = new User();
group = new Group({
name: 'test',
type: 'guild',
privacy: 'private',
leader: leader._id,
});
});
it('returns true if user is not subscribed', async () => {
expect(await user.canGetGems()).to.equal(true);
});
it('returns true if user is not subscribed with a group plan', async () => {
user.purchased.plan.customerId = 123;
expect(await user.canGetGems()).to.equal(true);
});
it('returns true if user is subscribed with a group plan', async () => {
user.purchased.plan.customerId = 'group-plan';
expect(await user.canGetGems()).to.equal(true);
});
it('returns true if user is part of a group', async () => {
user.guilds.push(group._id);
expect(await user.canGetGems()).to.equal(true);
});
it('returns true if user is part of a group with a subscription', async () => {
user.guilds.push(group._id);
user.purchased.plan.customerId = 'group-plan';
group.purchased.plan.customerId = 123;
await group.save();
expect(await user.canGetGems()).to.equal(true);
});
it('returns true if leader is part of a group with a subscription and canGetGems: false', async () => {
user.guilds.push(group._id);
user.purchased.plan.customerId = 'group-plan';
group.purchased.plan.customerId = 123;
group.leader = user._id;
group.leaderOnly.getGems = true;
await group.save();
expect(await user.canGetGems()).to.equal(true);
});
it('returns true if user is part of a group with no subscription but canGetGems: false', async () => {
user.guilds.push(group._id);
user.purchased.plan.customerId = 'group-plan';
group.leaderOnly.getGems = true;
await group.save();
expect(await user.canGetGems()).to.equal(true);
});
it('returns false if user is part of a group with a subscription and canGetGems: false', async () => {
user.guilds.push(group._id);
user.purchased.plan.customerId = 'group-plan';
group.purchased.plan.customerId = 123;
group.leaderOnly.getGems = true;
await group.save();
expect(await user.canGetGems()).to.equal(false);
});
});
context('hasNotCancelled', () => {
let user;
beforeEach(() => {

View File

@@ -6,6 +6,7 @@ describe('Notification Controller', function() {
beforeEach(function() {
user = specHelper.newUser();
user._id = "unique-user-id";
user.needsCron = false;
var userSync = sinon.stub().returns({
then: function then (f) { f(); }

View File

@@ -1,36 +1,36 @@
.promo_android {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1748px -176px;
background-position: -1748px 0px;
width: 175px;
height: 175px;
}
.promo_aquatic_potions {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -447px -148px;
background-position: -589px -148px;
width: 141px;
height: 441px;
}
.promo_backgrounds_armoire_201602 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1606px 0px;
background-position: -1606px -295px;
width: 141px;
height: 294px;
}
.promo_backgrounds_armoire_201603 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1606px -295px;
background-position: -1606px -590px;
width: 141px;
height: 294px;
}
.promo_backgrounds_armoire_201604 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: 0px -894px;
background-position: -1325px -441px;
width: 140px;
height: 441px;
}
.promo_backgrounds_armoire_201605 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -705px -894px;
background-position: -987px -894px;
width: 140px;
height: 441px;
}
@@ -60,25 +60,25 @@
}
.promo_backgrounds_armoire_201610 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -141px -894px;
background-position: -282px -894px;
width: 140px;
height: 441px;
}
.promo_backgrounds_armoire_201611 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -564px -894px;
background-position: -141px -894px;
width: 140px;
height: 441px;
}
.promo_backgrounds_armoire_201612 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -423px -894px;
background-position: 0px -894px;
width: 140px;
height: 441px;
}
.promo_backgrounds_armoire_201701 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -846px -894px;
background-position: -705px -894px;
width: 140px;
height: 441px;
}
@@ -102,31 +102,31 @@
}
.promo_backgrounds_armoire_201705 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1183px -442px;
background-position: -447px -148px;
width: 141px;
height: 441px;
}
.promo_backgrounds_armoire_201706 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -899px -442px;
background-position: -1183px -442px;
width: 141px;
height: 441px;
}
.promo_backgrounds_armoire_201707 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -899px 0px;
background-position: -899px -442px;
width: 141px;
height: 441px;
}
.promo_bees {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -757px -442px;
background-position: -899px 0px;
width: 141px;
height: 441px;
}
.promo_bundle_feathered {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -140px -305px;
background-position: -757px -442px;
width: 141px;
height: 441px;
}
@@ -150,7 +150,7 @@
}
.promo_checkin_incentives {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1606px -590px;
background-position: -1606px 0px;
width: 141px;
height: 294px;
}
@@ -162,7 +162,7 @@
}
.promo_classes_fall_2015 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -558px -1336px;
background-position: -823px -1336px;
width: 377px;
height: 99px;
}
@@ -228,7 +228,7 @@
}
.promo_cow {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -282px -894px;
background-position: -564px -894px;
width: 140px;
height: 441px;
}
@@ -252,7 +252,7 @@
}
.promo_enchanted_armoire {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -936px -1336px;
background-position: -1201px -1336px;
width: 374px;
height: 76px;
}
@@ -270,7 +270,7 @@
}
.promo_enchanted_armoire_201509 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1543px -1484px;
background-position: -1088px -1484px;
width: 90px;
height: 90px;
}
@@ -282,13 +282,13 @@
}
.promo_enchanted_armoire_201601 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1634px -1484px;
background-position: -906px -1484px;
width: 90px;
height: 90px;
}
.promo_fairy_potions {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -589px -148px;
background-position: -140px -305px;
width: 141px;
height: 441px;
}
@@ -300,13 +300,13 @@
}
.promo_ghost_potions {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -987px -894px;
background-position: -846px -894px;
width: 140px;
height: 441px;
}
.promo_habitica {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1748px 0px;
background-position: -1748px -176px;
width: 175px;
height: 175px;
}
@@ -318,7 +318,7 @@
}
.promo_habitoween_2016 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1325px -441px;
background-position: -423px -894px;
width: 140px;
height: 441px;
}
@@ -346,6 +346,12 @@
width: 276px;
height: 147px;
}
.promo_king_manta {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -558px -1336px;
width: 264px;
height: 147px;
}
.promo_more_checkin_incentives {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -306px 0px;
@@ -354,7 +360,7 @@
}
.promo_mystery_201405 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -906px -1484px;
background-position: -1543px -1484px;
width: 90px;
height: 90px;
}
@@ -378,7 +384,7 @@
}
.promo_mystery_201409 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -341px -1585px;
background-position: -1634px -1484px;
width: 90px;
height: 90px;
}
@@ -390,7 +396,7 @@
}
.promo_mystery_201411 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -815px -1484px;
background-position: -614px -1585px;
width: 90px;
height: 90px;
}
@@ -408,7 +414,7 @@
}
.promo_mystery_201502 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1088px -1484px;
background-position: -815px -1484px;
width: 90px;
height: 90px;
}
@@ -438,13 +444,13 @@
}
.promo_mystery_201507 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1467px -879px;
background-position: -1467px -773px;
width: 90px;
height: 105px;
}
.promo_mystery_201508 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1467px -1167px;
background-position: -140px -747px;
width: 93px;
height: 90px;
}
@@ -456,7 +462,7 @@
}
.promo_mystery_201510 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -140px -747px;
background-position: -721px -1484px;
width: 93px;
height: 90px;
}
@@ -504,13 +510,13 @@
}
.promo_mystery_201606 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1467px -773px;
background-position: -1467px -879px;
width: 90px;
height: 105px;
}
.promo_mystery_201607 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -614px -1585px;
background-position: -341px -1585px;
width: 90px;
height: 90px;
}
@@ -522,7 +528,7 @@
}
.promo_mystery_201609 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -721px -1484px;
background-position: -1467px -1167px;
width: 93px;
height: 90px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 936 KiB

After

Width:  |  Height:  |  Size: 928 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -646,7 +646,7 @@ form
// Task filters
// --------
.task-filter
margin: 1.5em 0 1em 0
margin: 1em 0 1em 0
hrpg-button-bar-mixin($color-tasks)
@extend $hrpg-button-bar
width: 100%

View File

@@ -146,6 +146,13 @@ window.habitrpg = angular.module('habitrpg',
title: env.t('groupPlansTitle')
})
.state('options.social.newGroup', {
url: '/new-group',
templateUrl: "partials/options.social.newGroup.html",
controller: 'NewGroupCtrl',
title: env.t('newGroupTitle')
})
.state('options.social.guilds', {
url: '/guilds',
templateUrl: "partials/options.social.guilds.html",

View File

@@ -9,7 +9,7 @@ habitrpg.controller('GroupApprovalsCtrl', ['$scope', 'Tasks',
};
$scope.approvalTitle = function (approval) {
return env.t('approvalTitle', {text: approval.text, userName: approval.userId.profile.name});
return env.t('approvalTitle', {type: approval.type, text: approval.text, userName: approval.userId.profile.name});
};
$scope.refreshApprovals = function () {

View File

@@ -9,35 +9,11 @@ angular.module('habitrpg')
function($scope, $window, Groups, Payments) {
$scope.PAGES = {
BENEFITS: 'benefits',
CREATE_GROUP: 'create-group',
UPGRADE_GROUP: 'upgrade-group',
};
$scope.activePage = $scope.PAGES.BENEFITS;
$scope.newGroup = {
type: 'guild',
privacy: 'private',
};
$scope.PAYMENTS = {
AMAZON: 'amazon',
STRIPE: 'stripe',
};
$scope.changePage = function (page) {
$scope.activePage = page;
$window.scrollTo(0, 0);
};
$scope.newGroupIsReady = function () {
return !!$scope.newGroup.name;
};
$scope.createGroup = function () {
$scope.changePage($scope.PAGES.UPGRADE_GROUP);
};
$scope.upgradeGroup = function (paymentType) {
var subscriptionKey = 'group_monthly'; // @TODO: Get from content API?
if (paymentType === $scope.PAYMENTS.STRIPE) Payments.showStripe({subscription: subscriptionKey, coupon: null, groupToCreate: $scope.newGroup});
if (paymentType === $scope.PAYMENTS.AMAZON) Payments.amazonPayments.init({type: 'subscription', subscription: subscriptionKey, coupon: null, groupToCreate: $scope.newGroup});
};
}]);

View File

@@ -0,0 +1,53 @@
"use strict";
/*
A controller to manage the New Group page
*/
angular.module('habitrpg')
.controller("NewGroupCtrl", ['$scope', '$window', 'Groups', 'Payments',
function ($scope, $window, Groups, Payments) {
$scope.PAGES = {
CREATE_GROUP: 'create-group',
UPGRADE_GROUP: 'upgrade-group',
};
$scope.activePage = $scope.PAGES.CREATE_GROUP;
$scope.changePage = function (page) {
$scope.activePage = page;
$window.scrollTo(0, 0);
};
$scope.newGroup = {
type: 'guild',
privacy: 'private',
};
$scope.PAYMENTS = {
AMAZON: 'amazon',
STRIPE: 'stripe',
};
$scope.newGroupIsReady = function () {
return !!$scope.newGroup.name;
};
$scope.createGroup = function () {
$scope.changePage($scope.PAGES.UPGRADE_GROUP);
};
$scope.upgradeGroup = function (paymentType) {
var subscriptionKey = 'group_monthly'; // @TODO: Get from content API?
if (paymentType === $scope.PAYMENTS.STRIPE) Payments.showStripe({
subscription: subscriptionKey,
coupon: null,
groupToCreate: $scope.newGroup
});
if (paymentType === $scope.PAYMENTS.AMAZON) Payments.amazonPayments.init({
type: 'subscription',
subscription: subscriptionKey,
coupon: null,
groupToCreate: $scope.newGroup
});
};
}]);

View File

@@ -1,8 +1,8 @@
'use strict';
habitrpg.controller('NotificationCtrl',
['$scope', '$rootScope', 'Shared', 'Content', 'User', 'Guide', 'Notification', 'Analytics', 'Achievement', 'Social', 'Tasks',
function ($scope, $rootScope, Shared, Content, User, Guide, Notification, Analytics, Achievement, Social, Tasks) {
['$scope', '$rootScope', 'Shared', 'Content', 'User', 'Guide', 'Notification', 'Analytics', 'Achievement', 'Social', 'Tasks', '$modal',
function ($scope, $rootScope, Shared, Content, User, Guide, Notification, Analytics, Achievement, Social, Tasks, $modal) {
var isRunningYesterdailies = false;
$rootScope.$watch('user', function (after, before) {
@@ -39,6 +39,7 @@ habitrpg.controller('NotificationCtrl',
if (yesterDailies.length === 0) {
User.runCron().then(function () {
isRunningYesterdailies = false;
handleUserNotifications(User.user);
});
return;
};
@@ -53,7 +54,8 @@ habitrpg.controller('NotificationCtrl',
modalScope.processingYesterdailies = true;
$scope.yesterDailiesModalOpen = true;
$rootScope.openModal('yesterDailies', {
$modal.open({
templateUrl: 'modals/yesterDailies.html',
scope: modalScope,
backdrop: 'static',
controller: ['$scope', 'Tasks', 'User', '$rootScope', function ($scope, Tasks, User, $rootScope) {
@@ -72,6 +74,7 @@ habitrpg.controller('NotificationCtrl',
User.runCron()
.then(function () {
isRunningYesterdailies = false;
handleUserNotifications(User.user);
});
};
}],
@@ -312,13 +315,14 @@ habitrpg.controller('NotificationCtrl',
// are now stored in user.notifications.
$rootScope.$watchCollection('userNotifications', function (after) {
if (!User.user._wrapped) return;
if (User.user.needsCron) return;
handleUserNotifications(after);
});
var handleUserNotificationsOnFirstSync = _.once(function () {
handleUserNotifications($rootScope.userNotifications);
});
$rootScope.$on('userUpdated', handleUserNotificationsOnFirstSync);
// var handleUserNotificationsOnFirstSync = _.once(function () {
// handleUserNotifications($rootScope.userNotifications);
// });
// $rootScope.$on('userUpdated', handleUserNotificationsOnFirstSync);
// TODO what about this?
$rootScope.$watch('user.achievements', function(){

View File

@@ -1,8 +1,8 @@
'use strict';
angular.module('habitrpg').factory('Payments',
['$rootScope', 'User', '$http', 'Content',
function($rootScope, User, $http, Content) {
['$rootScope', 'User', '$http', 'Content', 'Notification',
function($rootScope, User, $http, Content, Notification) {
var Payments = {};
var isAmazonReady = false;
Payments.amazonButtonEnabled = true;
@@ -21,7 +21,18 @@ function($rootScope, User, $http, Content) {
amazon.Login.setClientId(window.env.AMAZON_PAYMENTS.CLIENT_ID);
};
Payments.checkGemAmount = function(data) {
if (data && data.gift && data.gift.type === "gems" && (!data.gift.gems.amount || data.gift.gems.amount === 0)) {
Notification.error(window.env.t('badAmountOfGemsToPurchase'), true);
return false;
}
return true;
}
Payments.showStripe = function(data) {
if(!Payments.checkGemAmount(data)) return;
var sub = false;
if (data.subscription) {
@@ -121,6 +132,7 @@ function($rootScope, User, $http, Content) {
// Needs to be called everytime the modal/router is accessed
Payments.amazonPayments.init = function(data) {
if(!isAmazonReady) return;
if(!Payments.checkGemAmount(data)) return;
if(data.type !== 'single' && data.type !== 'subscription') return;
if (data.gift) {
@@ -345,6 +357,14 @@ function($rootScope, User, $http, Content) {
});
}
Payments.payPalPayment = function(data){
if(!Payments.checkGemAmount(data)) return;
var gift = Payments.encodeGift(data.giftedTo, data.gift);
var url = '/paypal/checkout?_id=' + User.user._id + '&apiToken=' + User.settings.auth.apiToken + '&gift=' + gift;
$http.get(url);
}
Payments.encodeGift = function(uuid, gift) {
gift.uuid = uuid;
var encodedString = JSON.stringify(gift);

View File

@@ -357,6 +357,9 @@ angular.module('habitrpg')
var numberToShortDay = Shared.DAY_MAPPING;
function generateSummary(task) {
if (task._edit.everyX === 0)
return window.env.t('repeatZero');
var frequencyPlural = frequencyMap[task._edit.frequency];
var repeatDays = '';
@@ -409,8 +412,8 @@ angular.module('habitrpg')
var dateFormat = 'MM-DD-YYYY';
if (user.preferences.dateFormat) dateFormat = user.preferences.dateFormat.toUpperCase();
var nextDue = nextDueDates.map(function (date) {
var nextDueDatesArr = angular.isArray(nextDueDates) ? nextDueDates : [];
var nextDue = nextDueDatesArr.map(function (date) {
return date.format(dateFormat);
});

View File

@@ -423,7 +423,7 @@ angular.module('habitrpg')
url: 'api/v3/cron',
})
.then(function (response) {
sync();
return sync();
})
},

View File

@@ -26,11 +26,20 @@ window.habitrpg = angular.module('habitrpg', ['chieffancypants.loadingBar', 'ui.
$http.defaults.headers.common['x-client'] = 'habitica-web';
}])
.controller("PlansCtrl", ['$rootScope','Analytics',
function($rootScope,Analytics) {
.controller("PlansCtrl", ['$rootScope','Analytics','$location','User','$scope',
function($rootScope,Analytics,$location,User,$scope) {
$rootScope.clickContact = function(){
Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Contact Us (Plans)'})
}
};
$scope.goToNewGroupPage = function () {
if (User.authenticated()) {
window.location.href="/#/options/groups/new-group";
} else {
// There is no authenticated user, so redirect to the login page,
// taking the hash with it to effectively redirect after login.
window.location.href = "/static/login#/options/groups/new-group";
}
};
}
])

View File

@@ -111,6 +111,7 @@
"js/controllers/tasksCtrl.js",
"js/controllers/userCtrl.js",
"js/controllers/groupPlansCtrl.js",
"js/controllers/newGroupCtrl.js",
"js/components/groupTasks/groupTasksController.js",
"js/components/groupTasks/groupTasksDirective.js",

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 B

View File

@@ -1,36 +1,36 @@
.promo_android {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1748px -176px;
background-position: -1748px 0px;
width: 175px;
height: 175px;
}
.promo_aquatic_potions {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -447px -148px;
background-position: -589px -148px;
width: 141px;
height: 441px;
}
.promo_backgrounds_armoire_201602 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1606px 0px;
background-position: -1606px -295px;
width: 141px;
height: 294px;
}
.promo_backgrounds_armoire_201603 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1606px -295px;
background-position: -1606px -590px;
width: 141px;
height: 294px;
}
.promo_backgrounds_armoire_201604 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: 0px -894px;
background-position: -1325px -441px;
width: 140px;
height: 441px;
}
.promo_backgrounds_armoire_201605 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -705px -894px;
background-position: -987px -894px;
width: 140px;
height: 441px;
}
@@ -60,25 +60,25 @@
}
.promo_backgrounds_armoire_201610 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -141px -894px;
background-position: -282px -894px;
width: 140px;
height: 441px;
}
.promo_backgrounds_armoire_201611 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -564px -894px;
background-position: -141px -894px;
width: 140px;
height: 441px;
}
.promo_backgrounds_armoire_201612 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -423px -894px;
background-position: 0px -894px;
width: 140px;
height: 441px;
}
.promo_backgrounds_armoire_201701 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -846px -894px;
background-position: -705px -894px;
width: 140px;
height: 441px;
}
@@ -102,31 +102,31 @@
}
.promo_backgrounds_armoire_201705 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1183px -442px;
background-position: -447px -148px;
width: 141px;
height: 441px;
}
.promo_backgrounds_armoire_201706 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -899px -442px;
background-position: -1183px -442px;
width: 141px;
height: 441px;
}
.promo_backgrounds_armoire_201707 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -899px 0px;
background-position: -899px -442px;
width: 141px;
height: 441px;
}
.promo_bees {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -757px -442px;
background-position: -899px 0px;
width: 141px;
height: 441px;
}
.promo_bundle_feathered {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -140px -305px;
background-position: -757px -442px;
width: 141px;
height: 441px;
}
@@ -150,7 +150,7 @@
}
.promo_checkin_incentives {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1606px -590px;
background-position: -1606px 0px;
width: 141px;
height: 294px;
}
@@ -162,7 +162,7 @@
}
.promo_classes_fall_2015 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -558px -1336px;
background-position: -823px -1336px;
width: 377px;
height: 99px;
}
@@ -228,7 +228,7 @@
}
.promo_cow {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -282px -894px;
background-position: -564px -894px;
width: 140px;
height: 441px;
}
@@ -252,7 +252,7 @@
}
.promo_enchanted_armoire {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -936px -1336px;
background-position: -1201px -1336px;
width: 374px;
height: 76px;
}
@@ -270,7 +270,7 @@
}
.promo_enchanted_armoire_201509 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1543px -1484px;
background-position: -1088px -1484px;
width: 90px;
height: 90px;
}
@@ -282,13 +282,13 @@
}
.promo_enchanted_armoire_201601 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1634px -1484px;
background-position: -906px -1484px;
width: 90px;
height: 90px;
}
.promo_fairy_potions {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -589px -148px;
background-position: -140px -305px;
width: 141px;
height: 441px;
}
@@ -300,13 +300,13 @@
}
.promo_ghost_potions {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -987px -894px;
background-position: -846px -894px;
width: 140px;
height: 441px;
}
.promo_habitica {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1748px 0px;
background-position: -1748px -176px;
width: 175px;
height: 175px;
}
@@ -318,7 +318,7 @@
}
.promo_habitoween_2016 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1325px -441px;
background-position: -423px -894px;
width: 140px;
height: 441px;
}
@@ -346,6 +346,12 @@
width: 276px;
height: 147px;
}
.promo_king_manta {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -558px -1336px;
width: 264px;
height: 147px;
}
.promo_more_checkin_incentives {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -306px 0px;
@@ -354,7 +360,7 @@
}
.promo_mystery_201405 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -906px -1484px;
background-position: -1543px -1484px;
width: 90px;
height: 90px;
}
@@ -378,7 +384,7 @@
}
.promo_mystery_201409 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -341px -1585px;
background-position: -1634px -1484px;
width: 90px;
height: 90px;
}
@@ -390,7 +396,7 @@
}
.promo_mystery_201411 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -815px -1484px;
background-position: -614px -1585px;
width: 90px;
height: 90px;
}
@@ -408,7 +414,7 @@
}
.promo_mystery_201502 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1088px -1484px;
background-position: -815px -1484px;
width: 90px;
height: 90px;
}
@@ -438,13 +444,13 @@
}
.promo_mystery_201507 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1467px -879px;
background-position: -1467px -773px;
width: 90px;
height: 105px;
}
.promo_mystery_201508 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1467px -1167px;
background-position: -140px -747px;
width: 93px;
height: 90px;
}
@@ -456,7 +462,7 @@
}
.promo_mystery_201510 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -140px -747px;
background-position: -721px -1484px;
width: 93px;
height: 90px;
}
@@ -504,13 +510,13 @@
}
.promo_mystery_201606 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -1467px -773px;
background-position: -1467px -879px;
width: 90px;
height: 105px;
}
.promo_mystery_201607 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -614px -1585px;
background-position: -341px -1585px;
width: 90px;
height: 90px;
}
@@ -522,7 +528,7 @@
}
.promo_mystery_201609 {
background-image: url(/static/sprites/spritesmith-largeSprites-0.png);
background-position: -721px -1484px;
background-position: -1467px -1167px;
width: 93px;
height: 90px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -14,18 +14,18 @@
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
color: $white;
&:focus {
&:focus:not(.btn-flat) {
outline: none;
border-color: $purple-400;
@include btn-focus-hover-shadow();
}
&:hover {
&:hover:not(.btn-flat) {
@include btn-focus-hover-shadow();
border-color: transparent;
}
&:active, &.active {
&:active:not(.btn-flat), &.active:not(.btn-flat) {
box-shadow: none;
border: 1px solid transparent;
}
@@ -124,3 +124,8 @@
color: inherit !important;
}
}
.btn-flat {
border: 0;
box-shadow: none;
}

View File

@@ -38,9 +38,10 @@
&:focus {
outline: none;
background-color: inherit;
}
&:active, &:hover, &:focus, &.active {
&:active, &:hover, &.active {
background-color: rgba(#d5c8ff, 0.32);
color: $purple-200;
}

View File

@@ -12,4 +12,18 @@
* {
transition: none !important;
}
}
}
.icon-16 {
width: 16px;
height: 16px;
}
.icon-10 {
width: 10px;
height: 10px;
}
.inline {
display: inline-block;
}

View File

@@ -34,10 +34,18 @@
cursor: auto;
}
&-active {
background: $purple-500;
}
&:hover {
box-shadow: 0 4px 4px 0 rgba($black, 0.16), 0 1px 8px 0 rgba($black, 0.12);
border-color: $purple-500;
}
&.highlight {
box-shadow: 0 0 8px 8px rgba($black, 0.16), 0 5px 10px 0 rgba($black, 0.12) !important;
}
}
.drawer-content .item:hover {

View File

@@ -12,10 +12,12 @@ html, body {
padding: 24px;
font-size: 14px;
line-height: 1.43;
width: 236px;
}
.standard-page {
padding: 24px;
flex: 1;
}
.page-header {

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="23" viewBox="0 0 40 23">
<g fill="none" fill-rule="evenodd" stroke="#A5A1AC" stroke-width="2.4">
<path d="M23.324 10.53h14.621M2.248 10.53h21.946M16.804 15.667s1.501-.742 3.293-.742a7.57 7.57 0 0 1 3.197.742"/>
<path stroke-linecap="round" stroke-linejoin="round" d="M16.838 10.53v3.878c0 3.879-3.22 7.052-7.154 7.052H8.359c-3.497 0-6.36-2.822-6.36-6.269v-4.968l8.289-7.205c2.02-1.756 4.63-1.113 6.482.958M23.26 10.53v3.878c0 3.879 3.219 7.052 7.154 7.052h1.325c3.497 0 6.359-2.822 6.359-6.269v-4.968L29.81 3.018c-2.02-1.756-4.63-1.113-6.482.958"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 644 B

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="34" viewBox="0 0 32 34">
<g fill="none" fill-rule="evenodd">
<path class='path' stroke="#6133B4" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.4" d="M2.124 20.456S2.854 2 15.734 2c12.88 0 13.61 18.456 13.61 18.456"/>
<path class='path' stroke="#6133B4" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.4" d="M10.29 32.388s-1.725-7.96-1.725-14.681c0-6.722 2.382-14.682 2.382-14.682M21.064 32.388s1.724-7.96 1.724-14.681c0-6.722-2.382-14.682-2.382-14.682"/>
<path class='path' stroke="#6133B4" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.4" d="M8.957 22.65s3.372-.64 6.933-.64c3.563 0 6.54.64 6.54.64M7.003 32.388h3.29M21.144 32.388h3.289"/>
<path fill="#6133B4" d="M3.784 21.533a1.893 1.893 0 1 1-3.785 0 1.893 1.893 0 0 1 3.785 0M31.237 21.533a1.893 1.893 0 1 1-3.786 0 1.893 1.893 0 0 1 3.786 0"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 960 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
<g fill="none" fill-rule="evenodd" stroke="#A5A1AC" stroke-width="2">
<path d="M1 11L11 1M11 11L1 1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 215 B

View File

@@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="144" viewBox="0 0 400 144">
<defs>
<path id="a" d="M0 0h400v144H0z"/>
<path id="b" d="M0 0h10v10H0z"/>
<path id="c" d="M0 0h10v10H0z"/>
<path id="d" d="M0 0h10v10H0z"/>
<path id="e" d="M0 0h10v10H0z"/>
</defs>
<g fill="none" fill-rule="evenodd">
<g>
<use fill="#FFF" xlink:href="#a"/>
<path stroke="#FFA623" stroke-width="4" d="M2 2h396v140H2z"/>
<path stroke="#B36213" stroke-width="2" d="M1 1h398v142H1z"/>
</g>
<g transform="translate(0 134)">
<use fill="#FFBE5D" xlink:href="#b"/>
<path stroke="#FFA623" stroke-width="4" d="M2 2h6v6H2z"/>
<path stroke="#B36213" stroke-width="2" d="M1 1h8v8H1z"/>
</g>
<g transform="translate(390 134)">
<use fill="#FFBE5D" xlink:href="#c"/>
<path stroke="#FFA623" stroke-width="4" d="M2 2h6v6H2z"/>
<path stroke="#B36213" stroke-width="2" d="M1 1h8v8H1z"/>
</g>
<g transform="translate(390)">
<use fill="#FFBE5D" xlink:href="#d"/>
<path stroke="#FFA623" stroke-width="4" d="M2 2h6v6H2z"/>
<path stroke="#B36213" stroke-width="2" d="M1 1h8v8H1z"/>
</g>
<g>
<use fill="#FFBE5D" xlink:href="#e"/>
<path stroke="#FFA623" stroke-width="4" d="M2 2h6v6H2z"/>
<path stroke="#B36213" stroke-width="2" d="M1 1h8v8H1z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="35" height="35" viewBox="0 0 35 35">
<g fill="none" fill-rule="evenodd">
<path fill="#A5A1AC" d="M22.688 0a7.222 7.222 0 0 0-5.12 2.12L2.12 17.569a7.241 7.241 0 0 0 0 10.24l2.538 2.538L30.346 4.66 27.809 2.12A7.222 7.222 0 0 0 22.688 0m0 2.414c1.289 0 2.502.502 3.413 1.414l.832.83L4.659 26.934l-.831-.831a4.793 4.793 0 0 1-1.415-3.414c0-1.29.502-2.501 1.415-3.414L19.275 3.828a4.793 4.793 0 0 1 3.413-1.414"/>
<path stroke="#A5A1AC" stroke-width="2.4" d="M4.385 28.385l5.746 5.747M28.36 4.41l5.746 5.746M16.372 16.398l5.746 5.746M7.382 25.389l5.746 5.746M10.379 22.392l5.746 5.746M13.376 19.395l5.746 5.746M19.37 13.4l5.745 5.747M22.366 10.404l5.746 5.746M25.363 7.407l5.746 5.746"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 768 B

View File

@@ -0,0 +1,49 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="191" height="42" viewBox="0 0 191 42">
<defs>
<path id="a" d="M.666 28.718h22.448V.606H.666v28.112z"/>
<path id="c" d="M.558 40.74h24.35V.077H.557z"/>
<path id="e" d="M0 40.74h24.03V.077H0V40.74z"/>
<path id="g" d="M19.379.077H.279v40.662h19.1z"/>
<path id="i" d="M6.59.077H.558v6.01H6.59V.077z"/>
<path id="k" d="M6.59.077H.558v6.01H6.59V.077z"/>
</defs>
<g fill="none" fill-rule="evenodd">
<g transform="translate(138.726 12.613)">
<mask id="b" fill="#fff">
<use xlink:href="#a"/>
</mask>
<path fill="#4F2A93" d="M21.94 19.774a3.025 3.025 0 0 0-4.23.545 6.115 6.115 0 0 1-4.87 2.389c-3.249 0-5.934-2.519-6.131-5.743-.007-.465-.012-3.793-.012-4.226 0-3.376 2.756-6.122 6.144-6.122a6.1 6.1 0 0 1 4.456 1.907 3.023 3.023 0 0 0 4.263.11 2.997 2.997 0 0 0 .11-4.249A12.248 12.248 0 0 0 12.84.606C6.128.606.667 6.048.667 12.739c0 .041.001 4.206.018 4.512.35 6.43 5.69 11.467 12.157 11.467a12.11 12.11 0 0 0 9.645-4.728 2.999 2.999 0 0 0-.546-4.216" mask="url(#b)"/>
</g>
<path fill="#4F2A93" d="M178.606 35.32c-3.249 0-5.933-2.518-6.131-5.74-.007-.467-.012-3.81-.012-4.229 0-3.375 2.756-6.122 6.143-6.122 3.388 0 6.144 2.747 6.144 6.122 0 .434-.005 3.763-.012 4.223-.195 3.226-2.88 5.746-6.132 5.746m9.16-22.621a3.007 3.007 0 0 0-2.911 2.259 12.12 12.12 0 0 0-6.249-1.74c-6.713 0-12.174 5.444-12.174 12.133 0 .043.002 4.205.017 4.513.35 6.43 5.69 11.467 12.157 11.467 2.341 0 4.53-.667 6.392-1.815a3.014 3.014 0 0 0 2.768 1.815 3.01 3.01 0 0 0 3.015-3.006V15.704a3.01 3.01 0 0 0-3.015-3.005M40.6 35.32c-3.25 0-5.934-2.519-6.13-5.742-.008-.464-.013-3.793-.013-4.227 0-3.375 2.756-6.122 6.144-6.122 3.387 0 6.143 2.747 6.143 6.122 0 .434-.005 3.763-.012 4.223-.195 3.226-2.88 5.746-6.131 5.746m9.159-22.621a3.007 3.007 0 0 0-2.91 2.259 12.12 12.12 0 0 0-6.25-1.74c-6.713 0-12.175 5.444-12.175 12.133 0 .043.002 4.206.019 4.513.35 6.43 5.69 11.467 12.157 11.467 2.34 0 4.53-.667 6.392-1.815a3.014 3.014 0 0 0 2.767 1.815 3.01 3.01 0 0 0 3.016-3.006V15.704A3.01 3.01 0 0 0 49.76 12.7M94.159 12.699a3.01 3.01 0 0 0-3.016 3.005v22.621a3.01 3.01 0 0 0 3.016 3.006 3.01 3.01 0 0 0 3.016-3.006v-22.62a3.01 3.01 0 0 0-3.016-3.006M129.678 12.699a3.01 3.01 0 0 0-3.016 3.005v22.621a3.01 3.01 0 0 0 3.016 3.006 3.01 3.01 0 0 0 3.016-3.006v-22.62a3.01 3.01 0 0 0-3.016-3.006"/>
<g transform="translate(59.646 .591)">
<mask id="d" fill="#fff">
<use xlink:href="#c"/>
</mask>
<path fill="#4F2A93" d="M18.864 28.99c-.198 3.221-2.883 5.739-6.131 5.739-3.25 0-5.935-2.519-6.132-5.746-.008-.464-.012-3.79-.012-4.223 0-3.376 2.756-6.122 6.144-6.122 3.386 0 6.143 2.746 6.143 6.122 0 .419-.005 3.762-.012 4.23m-6.131-16.363c-2.242 0-4.338.618-6.144 1.676V3.083A3.01 3.01 0 0 0 3.574.076 3.01 3.01 0 0 0 .558 3.082v34.653a3.01 3.01 0 0 0 3.016 3.005c1.24 0 2.304-.748 2.767-1.815a12.149 12.149 0 0 0 6.392 1.815c6.466 0 11.806-5.037 12.157-11.478.015-.297.017-4.46.017-4.502 0-6.69-5.462-12.133-12.174-12.133" mask="url(#d)"/>
</g>
<g transform="translate(0 .591)">
<mask id="f" fill="#fff">
<use xlink:href="#e"/>
</mask>
<path fill="#4F2A93" d="M11.855 12.627c-2.08 0-4.07.52-5.823 1.47V3.082A3.01 3.01 0 0 0 3.016.077 3.01 3.01 0 0 0 0 3.082v34.652a3.01 3.01 0 0 0 3.016 3.006 3.01 3.01 0 0 0 3.016-3.006V22.201c.23-.196.442-.416.612-.684a6.12 6.12 0 0 1 5.211-2.879c3.387 0 6.143 2.746 6.143 6.122 0 .55-.005 12.23-.013 12.9a3.01 3.01 0 0 0 3.018 3.08 3.012 3.012 0 0 0 3.01-2.853c.014-.297.016-12.999.016-13.127 0-6.69-5.46-12.133-12.174-12.133" mask="url(#f)"/>
</g>
<g transform="translate(101.867 .591)">
<mask id="h" fill="#fff">
<use xlink:href="#g"/>
</mask>
<path fill="#4F2A93" d="M16.363 12.108h-3.518V3.082A3.01 3.01 0 0 0 9.829.077a3.01 3.01 0 0 0-3.016 3.005v9.026H3.295a3.01 3.01 0 0 0-3.016 3.005 3.01 3.01 0 0 0 3.016 3.005h3.518v19.616a3.01 3.01 0 0 0 3.016 3.005 3.01 3.01 0 0 0 3.016-3.005V18.118h3.518a3.01 3.01 0 0 0 3.016-3.005 3.01 3.01 0 0 0-3.016-3.005" mask="url(#h)"/>
</g>
<g transform="translate(90.474 .591)">
<mask id="j" fill="#fff">
<use xlink:href="#i"/>
</mask>
<path fill="#FF6066" d="M6.59 3.082a3.01 3.01 0 0 1-3.016 3.005A3.01 3.01 0 0 1 .558 3.082 3.01 3.01 0 0 1 3.574.077 3.01 3.01 0 0 1 6.59 3.082" mask="url(#j)"/>
</g>
<g transform="translate(125.993 .591)">
<mask id="l" fill="#fff">
<use xlink:href="#k"/>
</mask>
<path fill="#4FB5E8" d="M6.59 3.082a3.01 3.01 0 0 1-3.016 3.005A3.01 3.01 0 0 1 .558 3.082 3.01 3.01 0 0 1 3.574.077 3.01 3.01 0 0 1 6.59 3.082" mask="url(#l)"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="12" viewBox="0 0 16 12">
<path fill="#4F2A93" fill-rule="evenodd" d="M14 10H2V2l6 5 6-5v8zm0-10H2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2z"/>
<path fill-rule="evenodd" d="M14 10H2V2l6 5 6-5v8zm0-10H2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2z"/>
</svg>

Before

Width:  |  Height:  |  Size: 235 B

After

Width:  |  Height:  |  Size: 220 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="37" height="33" viewBox="0 0 37 33">
<g fill="none" fill-rule="evenodd" stroke="#A5A1AC" stroke-linejoin="round" stroke-width="2.4">
<path stroke-linecap="round" d="M13.782 21.846s1.794 3.682 0 5.454c-1.793 1.773-5.957 0-8.327.938C3.085 29.175 2 31.65 2 31.65M23.744 21.846s-1.794 3.682 0 5.454c1.793 1.773 5.957 0 8.327.938 2.37.937 3.455 3.412 3.455 3.412"/>
<path d="M27.939 12.642c0 6.321-5.755 11.445-9.277 11.445-2.98 0-9.277-5.124-9.277-11.445C9.385 6.32 13.089 2 18.662 2c5.572 0 9.277 4.32 9.277 10.642z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 599 B

View File

@@ -1,54 +1,56 @@
<template lang="pug">
nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-sm
.navbar-header
.logo.svg-icon(v-html="icons.logo")
.collapse.navbar-collapse
ul.navbar-nav.mr-auto
router-link.nav-item(tag="li", :to="{name: 'tasks'}", exact)
a.nav-link(v-once) {{ $t('tasks') }}
router-link.nav-item.dropdown(tag="li", :to="{name: 'items'}", :class="{'active': $route.path.startsWith('/inventory')}")
a.nav-link(v-once) {{ $t('inventory') }}
.dropdown-menu
router-link.dropdown-item(:to="{name: 'items'}", exact) {{ $t('items') }}
router-link.dropdown-item(:to="{name: 'equipment'}") {{ $t('equipment') }}
router-link.dropdown-item(:to="{name: 'stable'}") {{ $t('stable') }}
router-link.nav-item(tag="li", :to="{name: 'shops'}", exact)
a.nav-link(v-once) {{ $t('shops') }}
router-link.nav-item(tag="li", :to="{name: 'party'}")
a.nav-link(v-once) {{ $t('party') }}
router-link.nav-item.dropdown(tag="li", :to="{name: 'tavern'}", :class="{'active': $route.path.startsWith('/guilds')}")
a.nav-link(v-once) {{ $t('guilds') }}
.dropdown-menu
router-link.dropdown-item(:to="{name: 'tavern'}") {{ $t('tavern') }}
router-link.dropdown-item(:to="{name: 'myGuilds'}") {{ $t('myGuilds') }}
router-link.dropdown-item(:to="{name: 'guildsDiscovery'}") {{ $t('guildsDiscovery') }}
router-link.nav-item(tag="li", :to="{name: 'challenges'}", exact)
a.nav-link(v-once) {{ $t('challenges') }}
router-link.nav-item.dropdown(tag="li", to="/help", :class="{'active': $route.path.startsWith('/help')}")
a.nav-link(v-once) {{ $t('help') }}
.dropdown-menu
router-link.dropdown-item(to="/help/faq") {{ $t('faq') }}
router-link.dropdown-item(to="/help/report-bug") {{ $t('reportBug') }}
router-link.dropdown-item(to="/help/request-feature") {{ $t('requestAF') }}
.item-with-icon
.svg-icon(v-html="icons.gem")
span {{userGems | roundBigNumber}}
.item-with-icon
.svg-icon(v-html="icons.gold")
span {{user.stats.gp | roundBigNumber}}
.item-with-icon.item-notifications
.svg-icon(v-html="icons.notifications")
router-link.dropdown.item-with-icon.item-user(:to="{name: 'avatar'}")
.svg-icon(v-html="icons.user")
.dropdown-menu.dropdown-menu-right.user-dropdown
router-link.dropdown-item.edit-avatar(:to="{name: 'avatar'}")
h3 {{ user.profile.name }}
span.small-text {{ $t('editAvatar') }}
router-link.dropdown-item(:to="{name: 'inbox'}") {{ $t('inbox') }}
router-link.dropdown-item(:to="{name: 'stats'}") {{ $t('stats') }}
router-link.dropdown-item(:to="{name: 'achievements'}") {{ $t('achievements') }}
router-link.dropdown-item(:to="{name: 'settings'}") {{ $t('settings') }}
a.nav-link.dropdown-item(to="/", @click.prevent='logout()') {{ $t('logout') }}
div
inbox-modal
nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-sm
.navbar-header
.logo.svg-icon(v-html="icons.logo")
.collapse.navbar-collapse
ul.navbar-nav.mr-auto
router-link.nav-item(tag="li", :to="{name: 'tasks'}", exact)
a.nav-link(v-once) {{ $t('tasks') }}
router-link.nav-item.dropdown(tag="li", :to="{name: 'items'}", :class="{'active': $route.path.startsWith('/inventory')}")
a.nav-link(v-once) {{ $t('inventory') }}
.dropdown-menu
router-link.dropdown-item(:to="{name: 'items'}", exact) {{ $t('items') }}
router-link.dropdown-item(:to="{name: 'equipment'}") {{ $t('equipment') }}
router-link.dropdown-item(:to="{name: 'stable'}") {{ $t('stable') }}
router-link.nav-item(tag="li", :to="{name: 'shops'}", exact)
a.nav-link(v-once) {{ $t('shops') }}
router-link.nav-item(tag="li", :to="{name: 'party'}")
a.nav-link(v-once) {{ $t('party') }}
router-link.nav-item.dropdown(tag="li", :to="{name: 'tavern'}", :class="{'active': $route.path.startsWith('/guilds')}")
a.nav-link(v-once) {{ $t('guilds') }}
.dropdown-menu
router-link.dropdown-item(:to="{name: 'tavern'}") {{ $t('tavern') }}
router-link.dropdown-item(:to="{name: 'myGuilds'}") {{ $t('myGuilds') }}
router-link.dropdown-item(:to="{name: 'guildsDiscovery'}") {{ $t('guildsDiscovery') }}
router-link.nav-item(tag="li", :to="{name: 'challenges'}", exact)
a.nav-link(v-once) {{ $t('challenges') }}
router-link.nav-item.dropdown(tag="li", to="/help", :class="{'active': $route.path.startsWith('/help')}")
a.nav-link(v-once) {{ $t('help') }}
.dropdown-menu
router-link.dropdown-item(to="/help/faq") {{ $t('faq') }}
router-link.dropdown-item(to="/help/report-bug") {{ $t('reportBug') }}
router-link.dropdown-item(to="/help/request-feature") {{ $t('requestAF') }}
.item-with-icon
.svg-icon(v-html="icons.gem")
span {{userGems | roundBigNumber}}
.item-with-icon
.svg-icon(v-html="icons.gold")
span {{user.stats.gp | roundBigNumber}}
.item-with-icon.item-notifications
.svg-icon(v-html="icons.notifications")
router-link.dropdown.item-with-icon.item-user(:to="{name: 'avatar'}")
.svg-icon(v-html="icons.user")
.dropdown-menu.dropdown-menu-right.user-dropdown
router-link.dropdown-item.edit-avatar(:to="{name: 'avatar'}")
h3 {{ user.profile.name }}
span.small-text {{ $t('editAvatar') }}
a.nav-link.dropdown-item(@click.prevent='showInbox()') {{ $t('inbox') }}
router-link.dropdown-item(:to="{name: 'stats'}") {{ $t('stats') }}
router-link.dropdown-item(:to="{name: 'achievements'}") {{ $t('achievements') }}
router-link.dropdown-item(:to="{name: 'settings'}") {{ $t('settings') }}
a.nav-link.dropdown-item(to="/", @click.prevent='logout()') {{ $t('logout') }}
</template>
<style lang="scss" scoped>
@@ -198,8 +200,12 @@ import goldIcon from 'assets/svg/gold.svg';
import notificationsIcon from 'assets/svg/notifications.svg';
import userIcon from 'assets/svg/user.svg';
import logo from 'assets/svg/logo.svg';
import InboxModal from './userMenu/inbox.vue';
export default {
components: {
InboxModal,
},
data () {
return {
icons: Object.freeze({
@@ -222,6 +228,9 @@ export default {
localStorage.removeItem('habit-mobile-settings');
this.$router.go('/');
},
showInbox () {
this.$root.$emit('show::modal', 'inbox-modal');
},
},
};
</script>

View File

@@ -1,7 +1,7 @@
<template lang="pug">
.avatar(:style="{width, height, paddingTop}", :class="backgroundClass")
.character-sprites
template(v-if="!avatarOnly" v-once)
template(v-if="!avatarOnly")
// Mount Body
span(v-if="member.items.currentMount", :class="'Mount_Body_' + member.items.currentMount")
@@ -35,7 +35,7 @@
// Resting
span.zzz(v-if="member.preferences.sleep")
template(v-if="!avatarOnly" v-once)
template(v-if="!avatarOnly")
// Mount Head
span(v-if="member.items.currentMount", :class="'Mount_Head_' + member.items.currentMount")
// Pet
@@ -168,4 +168,4 @@ export default {
},
},
};
</script>
</script>

View File

@@ -0,0 +1,451 @@
<template lang="pug">
#creator-background
#creator-modal
.section.row.welcome-section(v-if='modalPage == 1')
.col-6.offset-3.text-center
h3(v-once) {{$t('welcomeTo')}}
.svg-icon.logo(v-html='icons.logoPurple')
.section.row
.col-6.offset-3
.user-creation-bg
avatar(:member='user')
div(v-if='modalPage == 2')
.section.row
.col-12.text-center
button.btn.btn-secondary(v-once) {{$t('randomize')}}
.section.row.text-center.customize-menu
.col-3
.menu-item(@click='changeTopPage("body", "size")')
.svg-icon(v-html='icons.bodyIcon')
strong(v-once) {{$t('body')}}
.col-3
.menu-item(@click='changeTopPage("skin", "color")')
.svg-icon(v-html='icons.skinIcon')
strong(v-once) {{$t('skin')}}
.col-3
.menu-item(@click='changeTopPage("hair", "color")')
.svg-icon(v-html='icons.hairIcon')
strong(v-once) {{$t('hair')}}
.col-3
.menu-item(@click='changeTopPage("extra", "glasses")')
.svg-icon(v-html='icons.accessoriesIcon')
strong(v-once) {{$t('extra')}}
.section.customize-section(v-if='activeTopPage === "body"')
.row.sub-menu
.col-2.offset-4.sub-menu-item(@click='changeSubPage("size")', :class='{active: activeSubPage === "size"}')
strong(v-once) {{$t('size')}}
.col-2.sub-menu-item(@click='changeSubPage("shirt")', :class='{active: activeSubPage === "shirt"}')
strong(v-once) {{$t('shirt')}}
.row(v-if='activeSubPage === "size"')
.col-12.customize-options.size-options
.slim_shirt_black.option(@click='set({"preferences.size":"slim"})', :class='{active: user.preferences.size === "slim"}')
.broad_shirt_black.option(@click='set({"preferences.size":"broad"})', :class='{active: user.preferences.size === "broad"}')
.row(v-if='activeSubPage === "shirt"')
.col-12.customize-options
.slim_shirt_black.option(@click='set({"preferences.shirt":"black"})', :class='{active: user.preferences.shirt === "black"}')
.slim_shirt_blue.option(@click='set({"preferences.shirt":"blue"})', :class='{active: user.preferences.shirt === "blue"}')
.slim_shirt_green.option(@click='set({"preferences.shirt":"green"})', :class='{active: user.preferences.shirt === "green"}')
.slim_shirt_pink.option(@click='set({"preferences.shirt":"pink"})', :class='{active: user.preferences.shirt === "pink"}')
.slim_shirt_white.option(@click='set({"preferences.shirt":"white"})', :class='{active: user.preferences.shirt === "white"}')
.slim_shirt_yellow.option(@click='set({"preferences.shirt":"yellow"})', :class='{active: user.preferences.shirt === "yellow"}')
.section.customize-section(v-if='activeTopPage === "skin"')
.row.sub-menu
.col-6.offset-3.text-center.sub-menu-item(:class='{active: activeSubPage === "color"}')
strong(v-once) {{$t('color')}}
.row
.col-12.customize-options
.skin_ddc994.option(@click='set({"preferences.skin":"ddc994"})', :class='{active: user.preferences.skin === "ddc994"}')
.skin_f5a76e.option(@click='set({"preferences.skin":"f5a76e"})', :class='{active: user.preferences.skin === "f5a76e"}')
.skin_ea8349.option(@click='set({"preferences.skin":"ea8349"})', :class='{active: user.preferences.skin === "ea8349"}')
.skin_c06534.option(@click='set({"preferences.skin":"c06534"})', :class='{active: user.preferences.skin === "c06534"}')
.skin_98461a.option(@click='set({"preferences.skin":"98461a"})', :class='{active: user.preferences.skin === "98461a"}')
.skin_915533.option(@click='set({"preferences.skin":"915533"})', :class='{active: user.preferences.skin === "915533"}')
.skin_c3e1dc.option(@click='set({"preferences.skin":"c3e1dc"})', :class='{active: user.preferences.skin === "c3e1dc"}')
.skin_6bd049.option(@click='set({"preferences.skin":"6bd049"})', :class='{active: user.preferences.skin === "6bd049"}')
.section.customize-section(v-if='activeTopPage === "hair"')
.row.sub-menu
.col-2.offset-3.text-center.sub-menu-item(@click='changeSubPage("color")', :class='{active: activeSubPage === "color"}')
strong(v-once) {{$t('color')}}
.col-2.text-center.sub-menu-item(@click='changeSubPage("bangs")', :class='{active: activeSubPage === "bangs"}')
strong(v-once) {{$t('bangs')}}
.col-2.text-center.sub-menu-item(@click='changeSubPage("ponytail")', :class='{active: activeSubPage === "ponytail"}')
strong(v-once) {{$t('ponytail')}}
.row(v-if='activeSubPage === "color"')
.col-12.customize-options
.hair_bangs_1_white.option(@click='set({"preferences.hair.color": "white"})', :class='{active: user.preferences.hair.color === "white"}')
.hair_bangs_1_brown.option(@click='set({"preferences.hair.color": "brown"})', :class='{active: user.preferences.hair.color === "brown"}')
.hair_bangs_1_blond.option(@click='set({"preferences.hair.color": "blond"})', :class='{active: user.preferences.hair.color === "blond"}')
.hair_bangs_1_red.option(@click='set({"preferences.hair.color": "red"})', :class='{active: user.preferences.hair.color === "red"}')
.hair_bangs_1_black.option(@click='set({"preferences.hair.color": "black"})', :class='{active: user.preferences.hair.color === "black"}')
.row(v-if='activeSubPage === "bangs"')
.col-12.customize-options
.head_0.option(@click='set({"preferences.hair.bangs": 0})', :class="[{ active: user.preferences.hair.bangs === 0 }, 'hair_bangs_0_' + user.preferences.hair.color]")
.option(@click='set({"preferences.hair.bangs": 1})', :class="[{ active: user.preferences.hair.bangs === 1 }, 'hair_bangs_1_' + user.preferences.hair.color]")
.option(@click='set({"preferences.hair.bangs": 2})',:class="[{ active: user.preferences.hair.bangs === 2 }, 'hair_bangs_2_' + user.preferences.hair.color]")
.option(@click='set({"preferences.hair.bangs": 3})', :class="[{ active: user.preferences.hair.bangs === 3 }, 'hair_bangs_3_' + user.preferences.hair.color]")
.option(@click='set({"preferences.hair.bangs": 4})', :class="[{ active: user.preferences.hair.bangs === 4 }, 'hair_bangs_4_' + user.preferences.hair.color]")
.row(v-if='activeSubPage === "ponytail"')
.col-12.customize-options
.head_0.option(@click='set({"preferences.hair.base": 0})', :class="[{ active: user.preferences.hair.base === 0 }, 'hair_base_0_' + user.preferences.hair.color]")
.hair_base_1_blond.option(@click='set({"preferences.hair.base": 1})', :class="[{ active: user.preferences.hair.base === 1 }, 'hair_base_1_' + user.preferences.hair.color]")
.hair_base_3_blond.option(@click='set({"preferences.hair.base": 3})', :class="[{ active: user.preferences.hair.base === 3 }, 'hair_base_3_' + user.preferences.hair.color]")
.section.container.customize-section(v-if='activeTopPage === "extra"')
.row.sub-menu
.col-4.text-center.sub-menu-item(@click='changeSubPage("glasses")', :class='{active: activeSubPage === "glasses"}')
strong(v-once) {{$t('glasses')}}
.col-4.text-center.sub-menu-item(@click='changeSubPage("wheelchair")', :class='{active: activeSubPage === "wheelchair"}')
strong(v-once) {{$t('wheelchair')}}
.col-4.text-center.sub-menu-item(@click='changeSubPage("flower")', :class='{active: activeSubPage === "flower"}')
strong(v-once) {{$t('flower')}}
.row(v-if='activeSubPage === "glasses"')
.col-12.customize-options
.eyewear_special_blackTopFrame.option(@click='equip("eyewear_special_blackTopFrame")', :class='{active: user.preferences.costume ? user.items.gear.costume.eyewear === "eyewear_special_blackTopFrame" : user.items.gear.equipped.eyewear === "eyewear_special_blackTopFrame"}')
.eyewear_special_blueTopFrame.option(@click='equip("eyewear_special_blueTopFrame")', :class='{active: user.preferences.costume ? user.items.gear.costume.eyewear === "eyewear_special_blueTopFrame" : user.items.gear.equipped.eyewear === "eyewear_special_blueTopFrame"}')
.eyewear_special_greenTopFrame.option(@click='equip("eyewear_special_greenTopFrame")', :class='{active: user.preferences.costume ? user.items.gear.costume.eyewear === "eyewear_special_greenTopFrame" : user.items.gear.equipped.eyewear === "eyewear_special_greenTopFrame"}')
.eyewear_special_pinkTopFrame.option(@click='equip("eyewear_special_pinkTopFrame")', :class='{active: user.preferences.costume ? user.items.gear.costume.eyewear === "eyewear_special_pinkTopFrame" : user.items.gear.equipped.eyewear === "eyewear_special_pinkTopFrame"}')
.eyewear_special_redTopFrame.option(@click='equip("eyewear_special_redTopFrame")', :class='{active: user.preferences.costume ? user.items.gear.costume.eyewear === "eyewear_special_redTopFrame" : user.items.gear.equipped.eyewear === "eyewear_special_redTopFrame"}')
.eyewear_special_whiteTopFrame.option(@click='equip("eyewear_special_whiteTopFrame")', :class='{active: user.preferences.costume ? user.items.gear.costume.eyewear === "eyewear_special_whiteTopFrame" : user.items.gear.equipped.eyewear === "eyewear_special_whiteTopFrame"}')
.eyewear_special_yellowTopFrame.option(@click='equip("eyewear_special_yellowTopFrame")', :class='{active: user.preferences.costume ? user.items.gear.costume.eyewear === "eyewear_special_yellowTopFrame" : user.items.gear.equipped.eyewear === "eyewear_special_yellowTopFrame"}')
.row(v-if='activeSubPage === "wheelchair"')
.col-12.customize-options.weelchairs
.option(@click='set({"preferences.chair": "none"})', :class='{active: user.preferences.chair === "none"}')
| None
.option(@click='set({"preferences.chair": "black"})', :class='{active: user.preferences.chair === "black"}')
.button_chair_black
.option(@click='set({"preferences.chair": "blue"})', :class='{active: user.preferences.chair === "blue"}')
.button_chair_blue
.option(@click='set({"preferences.chair": "green"})', :class='{active: user.preferences.chair === "green"}')
.button_chair_green
.option(@click='set({"preferences.chair": "pink"})', :class='{active: user.preferences.chair === "pink"}')
.button_chair_pink
.option(@click='set({"preferences.chair": "red"})', :class='{active: user.preferences.chair === "red"}')
.button_chair_red
.option(@click='set({"preferences.chair": "yellow"})', :class='{active: user.preferences.chair === "yellow"}')
.button_chair_yellow
.row(v-if='activeSubPage === "flower"')
.col-12.customize-options
.head_0.option(@click='set({"preferences.hair.flower":0})', :class='{active: user.preferences.hair.flower === 0}')
.hair_flower_1.option(@click='set({"preferences.hair.flower":1})', :class='{active: user.preferences.hair.flower === 1}')
.hair_flower_2.option(@click='set({"preferences.hair.flower":2})', :class='{active: user.preferences.hair.flower === 2}')
.hair_flower_3.option(@click='set({"preferences.hair.flower":3})', :class='{active: user.preferences.hair.flower === 3}')
.hair_flower_4.option(@click='set({"preferences.hair.flower":4})', :class='{active: user.preferences.hair.flower === 4}')
.hair_flower_5.option(@click='set({"preferences.hair.flower":5})', :class='{active: user.preferences.hair.flower === 5}')
.hair_flower_6.option(@click='set({"preferences.hair.flower":6})', :class='{active: user.preferences.hair.flower === 6}')
.container.interests-section(v-if='modalPage == 3')
.section.row
.col-12.text-center
h2 I want to work on:
.section.row
.col-4.offset-2
div
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('work') }}
div
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('excercise') }}
div
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('health') }}
div
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('school') }}
.col-4
div
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('chores') }}
div
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('creativity') }}
div
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('budgeting') }}
.section.row.justin-message-section(:class='{top: modalPage > 1}')
.col-9
.justin-message(v-if='modalPage == 1')
p(v-once) {{$t('justinIntroMessage1')}}
p(v-once) {{$t('justinIntroMessage2')}}
.justin-message(v-if='modalPage > 1')
p(v-once) {{$t('justinIntroMessage3')}}
.section.container.footer
.row
.col-3.offset-1.text-center
div(v-if='modalPage > 1', @click='prev()')
.prev-arrow
.prev(v-once) {{$t('prev')}}
.col-4.text-center.circles
.circle(:class="{active: modalPage === 1}")
.circle(:class="{active: modalPage === 2}")
.circle(:class="{active: modalPage === 3}")
.col-3.text-center
div(v-if='modalPage < 3', @click='next()')
.next(v-once) {{$t('next')}}
.next-arrow
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
#creator-background {
background-color: $purple-200;
}
#creator-modal {
width: 448px;
height: 670px;
border-radius: 8px;
background-color: #ffffff;
box-shadow: 0 2px 16px 0 rgba(26, 24, 29, 0.32);
margin: 0 auto;
padding-top: 1em;
position: relative;
}
.section {
margin-top: 2em;
}
.welcome-section {
margin-top: 5em;
margin-bottom: 5em;
}
.logo {
width: 190px;
}
.user-creation-bg {
background-image: url('~client/assets/creator/creator-hills-bg.png');
height: 105px;
width: 219px;
}
.avatar {
position: absolute;
top: -23px;
left: 48px;
}
.justin-message {
background-image: url('~client/assets/svg/for-css/tutorial-border.svg');
height: 144px;
width: 400px;
padding: 2em;
margin-left: 1.5em;
}
.justin-message-section {
margin-top: 6em;
margin-bottom: 6em;
}
.justin-message-section.top {
position: absolute;
top: -15em;
}
.circles {
padding-left: 2em;
}
.circle {
width: 8px;
height: 8px;
background-color: #d8d8d8;
border-radius: 50%;
display: inline-block;
margin-right: 1em;
}
.circle.active {
background-color: #bda8ff;
}
.customize-menu {
.menu-item .svg-icon {
width: 32px;
height: 32px;
margin: 0 auto;
}
.menu-item:hover {
cursor: pointer;
svg path, strong {
stroke: purple !important;
}
}
}
.sub-menu:hover {
cursor: pointer;
}
.sub-menu-item {
text-align: center;
}
.sub-menu .sub-menu-item:hover, .sub-menu .sub-menu-item.active {
color: $purple-200;
border-bottom: 2px solid $purple-200;
}
.customize-options .option {
display: inline-block;
padding: 2em;
vertical-align: bottom;
}
.size-options {
padding-left: 9em;
}
.weelchairs .option {
width: 90px;
height: 90px;
}
.option.active {
border: 4px solid $purple-200;
border-radius: 4px;
margin-top: 1em;
}
.option:hover {
cursor: pointer;
}
.customize-section {
background-color: #f9f9f9;
padding-top: 1em;
height: 250px;
}
.interests-section {
margin-top: 7em;
}
.footer {
position: absolute;
padding-bottom: 1em;
bottom: 0;
width: 100%;
.prev {
color: #a5a1ac;
font-weight: bold;
display: inline-block;
padding: 0.4em;
margin-left: 1em;
}
.prev:hover, .prev-arrow:hover {
cursor: pointer;
}
.prev-arrow {
background-image: url('~client/assets/creator/prev.png');
width: 32px;
height: 32px;
display: inline-block;
vertical-align: bottom;
}
.next {
color: #6133b4;
font-weight: bold;
display: inline-block;
padding: 0.4em;
margin-right: 1em;
}
.next:hover, .next-arrow:hover {
cursor: pointer;
}
.next-arrow {
background-image: url('~client/assets/creator/arrow.png');
width: 32px;
height: 32px;
display: inline-block;
vertical-align: bottom;
}
}
</style>
<script>
// @TODO: Wait for my other PR (login/register) to fix the background and hiding the header
import { mapState } from 'client/libs/store';
import avatar from './avatar';
import logoPurple from 'assets/svg/logo-purple.svg';
import bodyIcon from 'assets/svg/body.svg';
import accessoriesIcon from 'assets/svg/accessories.svg';
import skinIcon from 'assets/svg/skin.svg';
import hairIcon from 'assets/svg/hair.svg';
export default {
components: {
avatar,
},
data () {
return {
icons: Object.freeze({
logoPurple,
bodyIcon,
accessoriesIcon,
skinIcon,
hairIcon,
}),
modalPage: 1,
activeTopPage: 'body',
activeSubPage: 'size',
};
},
computed: {
...mapState({user: 'user.data'}),
},
methods: {
prev () {
this.modalPage -= 1;
},
next () {
this.modalPage += 1;
},
changeTopPage (page, subpage) {
this.activeTopPage = page;
if (subpage) this.activeSubPage = subpage;
},
changeSubPage (page) {
this.activeSubPage = page;
},
set (settings) {
this.$store.dispatch('user:set', settings);
},
equip (key) {
this.$store.dispatch('common:equip', {key, type: 'costume'});
},
},
};
</script>

View File

@@ -2,7 +2,7 @@
.row
sidebar(@search="updateSearch", @filter="updateFilters")
.col-10.standard-page
.standard-page
.clearfix
h1.page-header.float-left(v-once) {{ $t('publicGuilds') }}
.float-right

View File

@@ -2,7 +2,7 @@
.row
sidebar(v-on:search="updateSearch", v-on:filter="updateFilters")
.col-10.no-guilds.standard-page(v-if='filteredGuilds.length === 0')
.no-guilds.standard-page(v-if='filteredGuilds.length === 0')
.no-guilds-wrapper
.svg-icon(v-html='icons.greyBadge')
h2 {{$t('noGuildsTitle')}}
@@ -10,7 +10,7 @@
p {{$t('noGuildsParagraph2')}}
span(v-if='loading') {{ $t('loading') }}
.col-10.standard-page(v-if='filteredGuilds.length > 0')
.standard-page(v-if='filteredGuilds.length > 0')
.row
.col-md-12
h1.page-header.float-left(v-once) {{ $t('myGuilds') }}

View File

@@ -1,5 +1,5 @@
<template lang="pug">
.col-2.standard-sidebar
.standard-sidebar
.form-group
input.form-control.search(type="text", :placeholder="$t('search')", v-model='searchTerm')

View File

@@ -1,209 +1,446 @@
<template lang="pug">
.row
.col-md-4
h2.ui.dividing.header SideMenu
.clearfix.col-8.standard-page
.row
.col-6.title-details
h1(v-once) {{ $t('welcomeToTavern') }}
.ui.card
.content
.header Daniel
.content
h4.ui.sub.header Activity
| Description
.extra.content
button.ui.button Rest at the Inn
.row.chat-row
.col-12
h3(v-once) {{ $t('welcomeToTavern') }}
.ui.card
.content
.header Resources
.content
textarea(:placeholder="$t('chatPlaceHolder')")
button.btn.btn-secondary.send-chat.float-right(v-once) {{ $t('send') }}
.container.community-guidelines(v-if='communityGuidelinesAccepted')
.row
div.col-8(v-once) {{ $t('communityGuidelinesIntro') }}
div.col-4
button.btn.btn-info(@click='acceptCommunityGuidelines()', v-once) {{ $t('acceptCommunityGuidelines') }}
.hr
.hr-middle(v-once) {{ $t('today') }}
.row
.col-md-2
.svg-icon(v-html="icons.like")
.col-md-10
.card(v-for="msg in group.chat", :key="msg.id")
.card-block
h3.leader Character name
span 2 hours ago
.clearfix
strong.float-left {{msg.user}}
.float-right {{msg.timestamp}}
.text {{msg.text}}
hr
span.action(v-once)
.svg-icon(v-html="icons.like")
| {{$t('like')}}
span.action(v-once)
.svg-icon(v-html="icons.copy")
| {{$t('copyAsTodo')}}
span.action(v-once)
.svg-icon(v-html="icons.report")
| {{$t('report')}}
span.action(v-once)
.svg-icon(v-html="icons.delete")
| {{$t('delete')}}
span.action.float-right
.svg-icon(v-html="icons.liked")
| +3
.col-md-4.sidebar
.section
.grassy-meadow-backdrop
.sleep
strong(v-once) {{ $t('sleepDescription') }}
ul
li Community Guidelines
li Looking for Group (Party Wanted) Posts
li FAQ
li Glossary
li Wiki
li Data Display Tool
li Report a Problem
li Request a Feature
li Community Forum
li Ask a Question (Habitica Help guild)
li(v-once) {{ $t('sleepBullet1') }}
li(v-once) {{ $t('sleepBullet2') }}
li(v-once) {{ $t('sleepBullet3') }}
li(v-once) {{ $t('sleepBullet4') }}
button.btn.btn-secondary.pause-button(v-if='!user.preferences.sleep', @click='toggleSleep()', v-once) {{ $t('pauseDailies') }}
button.btn.btn-secondary.pause-button(v-if='user.preferences.sleep', @click='toggleSleep()', v-once) {{ $t('unpauseDailies') }}
.ui.card
.content
.header Resources
.content
small
a(href='/#/options/groups/hall') Visit the Hall of Heroes (contributors and backers)
br
a(href='http://habitica.wikia.com/wiki/Contributor_Rewards', target='_blank') Learn more about contributor rewards
br
a(href='http://habitica.wikia.com/wiki/Contributing_to_Habitica', target='_blank') Learn how to contribute to Habitica
table.table.table-striped.panel-tiers
tbody
tr
td
a.label.label-contributor-1(ng-click='toggleUserTier($event)') Tier 1 (Friend)
div
p
span.achievement.achievement-boot
| When your
strong first
| set of submissions is deployed, you will receive the Habitica Contributor's badge. Your name in Tavern chat will proudly display that you are a contributor. As a bounty for your work, you will also receive
strong 3 Gems
| .
tr
td
a.label.label-contributor-2(ng-click='toggleUserTier($event)') Tier 2 (Friend)
div
p
span.shop-sprite.item-img.shop_armor_special_1
| When your
strong second
| set of submissions is deployed, the
strong Crystal Armor
| will be available for purchase in the Rewards shop. As a bounty for your continued work, you will also receive
strong 3 Gems.
tr
td
a.label.label-contributor-3(ng-click='toggleUserTier($event)') Tier 3 (Elite)
div
p
span.shop-sprite.item-img.shop_head_special_1
| When your
strong third
| set of submissions is deployed, the
strong Crystal Helmet
| will be available for purchase in the Rewards shop. As a bounty for your continued work, you will also receive
strong 3 Gems
| .
tr
td
a.label.label-contributor-4(ng-click='toggleUserTier($event)') Tier 4 (Elite)
div
p
span.shop-sprite.item-img.shop_weapon_special_1
| When your
strong fourth
| set of submissions is deployed, the
strong Crystal Sword
| will be available for purchase in the Rewards shop. As a bounty for your continued work, you will also receive
strong 4 Gems
| .
tr
td
a.label.label-contributor-5(ng-click='toggleUserTier($event)') Tier 5 (Champion)
div
p
span.shop-sprite.item-img.shop_shield_special_1
| When your
strong fifth
| set of submissions is deployed, the
strong Crystal Shield
| will be available for purchase in the Rewards shop. As a bounty for your continued work, you will also receive
strong 4 Gems
| .
tr
td
a.label.label-contributor-6(ng-click='toggleUserTier($event)') Tier 6 (Champion)
div
p
span.shop-sprite.item-img.Pet-Dragon-Hydra
| When your
strong sixth
| set of submissions is deployed, you will receive a
strong Hydra Pet
| . You will also receive
strong 4 Gems
| .
tr
td
a.label.label-contributor-7(ng-click='toggleUserTier($event)') Tier 7 (Legendary)
div
p
| When your
strong seventh
| set of submissions is deployed, you will receive
strong 4 Gems
| and become a member of the honored Contributor's Guild and be privy to the behind-the-scenes details of Habitica! Further contributions do not increase your tier, but you may continue to earn Gem bounties and titles.
tr
td
a.label.label-contributor-8(ng-click='toggleUserTier($event)') Moderator (Guardian)
div
p
| Moderators were selected carefully from high tier contributors, so please give them your respect and listen to their suggestions.
tr
td
a.label.label-contributor-9(ng-click='toggleUserTier($event)') Staff (Heroic)
div
p
| The Heroic tier contains Habitica staff and staff-level contributors. If you have this title, you were appointed to it (or hired!).
tr
td
a.label.label-npc(ng-click='toggleUserTier($event)') NPC
div
p
| NPCs backed Habitica's Kickstarter at the highest tier. You can find their avatars watching over site features!
.section-header
.row
.col-10
h3(v-once) {{ $t('staffAndModerators') }}
.col-2
.toggle-up(@click="sections.staff = !sections.staff", v-if="sections.staff")
.svg-icon(v-html="icons.upIcon")
.toggle-down(@click="sections.staff = !sections.staff", v-if="!sections.staff")
.svg-icon(v-html="icons.downIcon")
.section.row(v-if="sections.staff")
.col-3.staff(v-for='user in staff', :class='{staff: user.type === "Staff", moderator: user.type === "Moderator", bailey: user.name === "It\'s Bailey"}')
.title {{user.name}}
.type {{user.type}}
.ui.card
.content
.header Challenges
.content
.section-header
.row
.col-10
h3(v-once) {{ $t('helpfulLinks') }}
.col-2
.toggle-up(@click="sections.staff = !sections.staff", v-if="sections.staff")
.svg-icon(v-html="icons.upIcon")
.toggle-down(@click="sections.staff = !sections.staff", v-if="!sections.staff")
.svg-icon(v-html="icons.downIcon")
.section.row(v-if="sections.staff")
ul
li Challenge 1
li
a(herf='', v-once) {{ $t('communityGuidelinesLink') }}
li
a(herf='', v-once) {{ $t('lookingForGroup') }}
li
a(herf='', v-once) {{ $t('faq') }}
li
a(herf='', v-once) {{ $t('glossary') }}
li
a(herf='', v-once) {{ $t('wiki') }}
li
a(herf='', v-once) {{ $t('dataDisplayTool') }}
li
a(herf='', v-once) {{ $t('reportProblem') }}
li
a(herf='', v-once) {{ $t('requestFeature') }}
li
a(herf='', v-once) {{ $t('communityForum') }}
li
a(herf='', v-once) {{ $t('askQuestionGuild') }}
.col-md-8
h2.ui.dividing.header Tavern Chat
.ui.comments
.field
textarea(v-model='newMessage')
.ui.blue.labeled.submit.icon.button(v-on:click='sendChat')
i.icon.edit
| Send Chat
.comment(v-for="message in messages")
a.avatar
img(src='http://semantic-ui.com/images/avatar/small/matt.jpg')
.content
a.author {{message.from}}
.metadata
span.date {{message.date}}
.text
| {{message.message}}
.section-header
.row
.col-10
h3(v-once) {{ $t('playerTiers') }}
.col-2
.toggle-up(@click="sections.staff = !sections.staff", v-if="sections.staff")
.svg-icon(v-html="icons.upIcon")
.toggle-down(@click="sections.staff = !sections.staff", v-if="!sections.staff")
.svg-icon(v-html="icons.downIcon")
.section.row(v-if="sections.staff")
.col-12
p(v-once) {{ $t('playerTiersDesc') }}
ul.tier-list
li.tier1(v-once) {{ $t('tier1') }}
li.tier2(v-once) {{ $t('tier2') }}
li.tier3(v-once) {{ $t('tier3') }}
li.tier4(v-once) {{ $t('tier4') }}
li.tier5(v-once) {{ $t('tier5') }}
li.tier6(v-once) {{ $t('tier6') }}
li.tier7(v-once) {{ $t('tier7') }}
li.moderator(v-once) {{ $t('tierModerator') }}
li.staff(v-once) {{ $t('tierStaff') }}
li.npc(v-once) {{ $t('tierNPC') }}
</template>
<style lang='scss' scoped>
@import '~client/assets/scss/colors.scss';
// @TODO: Move chat to component
.chat-row {
position: relative;
.community-guidelines {
background-color: rgba(135, 129, 144, 0.84);
padding: 1em;
color: $white;
position: absolute;
top: 0;
height: 150px;
padding-top: 3em;
margin-top: 2.3em;
width: 98%;
border-radius: 4px;
}
textarea {
height: 150px;
width: 100%;
background-color: $white;
border: solid 1px $gray-400;
font-size: 16px;
font-style: italic;
line-height: 1.43;
color: $gray-300;
padding: .5em;
}
.hr {
width: 100%;
height: 20px;
border-bottom: 1px solid $gray-500;
text-align: center;
margin: 2em 0;
}
.hr-middle {
font-size: 16px;
font-weight: bold;
font-family: 'Roboto Condensed';
line-height: 1.5;
text-align: center;
color: $gray-200;
background-color: $gray-700;
padding: .2em;
margin-top: .2em;
display: inline-block;
width: 100px;
}
}
h1 {
color: $purple-200;
}
.sidebar {
background-color: $gray-600;
padding-top: 2em;
}
.toggle-up, .toggle-down {
width: 20px;
}
.toggle-up:hover, .toggle-down:hover {
cursor: pointer;
}
.pause-button {
background-color: #ffb445 !important;
color: $white;
width: 100%;
}
.section-header {
margin-top: 2em;
}
.grassy-meadow-backdrop {
background-image: url('~assets/images/groups/grassy-meadow-backdrop.png');
width: 472px;
height: 246px;
}
.sleep {
margin-top: 1em;
}
.staff {
margin-bottom: 1em;
.title {
color: #6133b4;
font-weight: bold;
}
}
.tier-list {
list-style-type: none;
padding: 0;
width: 98%;
li {
border-radius: 2px;
background-color: #edecee;
border: solid 1px #c3c0c7;
text-align: center;
padding: 1em;
margin-bottom: 1em;
}
.tier1 {
color: #c42870;
}
.tier2 {
color: #b01515;
}
.tier3 {
color: #d70e14;
}
.tier4 {
color: #c24d00;
}
.tier5 {
color: #9e650f;
}
.tier6 {
color: #2b8363;
}
.tier7 {
color: #167e87;
}
.moderator {
color: #277eab;
}
.staff {
color: #6133b4;
}
.npc {
color: $black;
}
}
.staff .title {
color: #6133b4;
}
.moderator .title {
color: #277eab;
}
.bailey .title {
color: $black;
}
</style>
<script>
import { mapState } from 'client/libs/store';
import deleteIcon from 'assets/svg/delete.svg';
import copyIcon from 'assets/svg/copy.svg';
import likeIcon from 'assets/svg/like.svg';
import likedIcon from 'assets/svg/liked.svg';
import reportIcon from 'assets/svg/report.svg';
import gemIcon from 'assets/svg/gem.svg';
import questIcon from 'assets/svg/quest.svg';
import challengeIcon from 'assets/svg/challenge.svg';
import informationIcon from 'assets/svg/information.svg';
import questBackground from 'assets/svg/quest-background-border.svg';
import upIcon from 'assets/svg/up.svg';
import downIcon from 'assets/svg/down.svg';
export default {
data () {
// TODO: Abstract to Store
let messages = [
{
from: 'Paglias',
fromUserId: 1234,
to: 'TheHollidayInn',
message: 'I love the Gang of Four',
date: new Date(),
},
];
return {
messages,
newMessage: '',
icons: Object.freeze({
like: likeIcon,
copy: copyIcon,
report: reportIcon,
delete: deleteIcon,
gem: gemIcon,
liked: likedIcon,
questIcon,
challengeIcon,
information: informationIcon,
questBackground,
upIcon,
downIcon,
}),
group: {
chat: [],
},
sections: {
staff: true,
},
staff: [
{
name: 'beffymaroo',
type: 'Staff',
},
{
name: 'lefnire',
type: 'Staff',
},
{
name: 'Lemoness',
type: 'Staff',
},
{
name: 'paglias',
type: 'Staff',
},
{
name: 'redphoenix',
type: 'Staff',
},
{
name: 'SabreCat',
type: 'Staff',
},
{
name: 'TheHollidayInn',
type: 'Staff',
},
{
name: 'viirus',
type: 'Staff',
},
{
name: 'It\'s Bailey',
type: 'Moderator',
},
{
name: 'Alys',
type: 'Moderator',
},
{
name: 'Blade',
type: 'Moderator',
},
{
name: 'Breadstrings',
type: 'Moderator',
},
{
name: 'Cantras',
type: 'Moderator',
},
{
name: 'Daniel the Bard',
type: 'Moderator',
},
{
name: 'deilann 5.0.5b',
type: 'Moderator',
},
{
name: 'Dewines',
type: 'Moderator',
},
{
name: 'Megan',
type: 'Moderator',
},
{
name: 'shanaqui',
type: 'Moderator',
},
],
};
},
computed: {
...mapState({user: 'user.data'}),
communityGuidelinesAccepted () {
return this.user.flags.communityGuidelinesAccepted;
},
},
mounted () {
// @TODO: Load tavern
},
methods: {
sendChat: function sendChat () {
// TODO: This will be default values based on the conversation and current user
let messageToSend = {
from: 'TheHollidayInn',
fromUserId: 3211,
to: 'Paglias',
message: this.newMessage,
date: new Date(),
};
this.messages.push(messageToSend);
this.newMessage = '';
aggreeToGuideLines () {
// @TODO:
},
pauseDailies () {
// @TODO:
},
acceptCommunityGuidelines () {
this.$store.dispatch('user:set', {'flags.communityGuidelinesAccepted': true});
},
toggleSleep () {
this.$store.dispatch('user:sleep');
},
},
};

View File

@@ -1,6 +1,6 @@
<template lang="pug">
.row
.col-2.standard-sidebar
.standard-sidebar
.form-group
input.form-control.input-search(type="text", v-model="searchText", :placeholder="$t('search')")
@@ -17,7 +17,7 @@
span.custom-control-indicator
span.custom-control-description(v-once) {{ group.label }}
.col-10.standard-page
.standard-page
.clearfix
h1.float-left.mb-0.page-header(v-once) {{ $t('equipment') }}
.float-right

View File

@@ -8,18 +8,15 @@ b-popover(
v-else,
:triggers="['hover']",
:placement="popoverPosition",
@click="click",
)
span(slot="content")
slot(name="popoverContent", :item="item")
.item-wrapper
.item-wrapper(@click="click")
.item
slot(name="itemBadge", :item="item")
span.item-content(
:class="itemContentClass",
:draggable="draggable",
@dragstart="onDrag"
:class="itemContentClass"
)
span.item-label(v-if="label") {{ label }}
</template>
@@ -49,22 +46,11 @@ export default {
type: String,
default: 'bottom',
},
draggable: {
type: Boolean,
default: false,
},
},
methods: {
click () {
this.$emit('click', this.item);
},
onDrag (ev) {
if (this.draggable) {
this.$emit('onDrag', ev);
} else {
ev.preventDefault();
}
},
},
};
</script>

View File

@@ -1,6 +1,6 @@
<template lang="pug">
.row
.col-2.standard-sidebar
.row(v-mousePosition="30", @mouseMoved="mouseMoved($event)")
.standard-sidebar
.form-group
input.form-control.input-search(type="text", v-model="searchText", :placeholder="$t('search')")
@@ -16,7 +16,7 @@
input.custom-control-input(type="checkbox", v-model="group.selected")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t(group.key) }}
.col-10.standard-page
.standard-page
.clearfix
h1.float-left.mb-0.page-header(v-once) {{ $t('items') }}
.float-right
@@ -34,16 +34,55 @@
|
span.badge.badge-pill.badge-default {{group.quantity}}
.items
.items(v-if="group.key === 'eggs'")
item(
v-for="({data: item, quantity}, index) in items[group.key]",
v-if="group.open || index < itemsPerLine",
:item="item",
:key="item.key",
:itemContentClass="`${group.classPrefix}${item.key}`"
:selected="true",
:itemContentClass="`${group.classPrefix}${item.key}`",
v-drag.drop.hatch="item.key",
@itemDragOver="onDragOver($event, item)",
@itemDropped="onDrop($event, item)",
@itemDragLeave="onDragLeave()",
@click="onEggClicked($event, item)"
)
template(slot="popoverContent", scope="ctx")
template(slot="popoverContent", scope="ctx")
h4.popover-content-title {{ ctx.item.text() }}
.popover-content-text {{ ctx.item.notes() }}
template(slot="itemBadge", scope="ctx")
span.badge.badge-pill.badge-item.badge-quantity {{ quantity }}
.items(v-else-if="group.key === 'hatchingPotions'")
item(
v-for="({data: item, quantity}, index) in items[group.key]",
v-if="group.open || index < itemsPerLine",
:item="item",
:key="item.key",
:itemContentClass="`${group.classPrefix}${item.key}`",
v-drag.hatch="item.key",
@itemDragEnd="onDragEnd($event, item)",
@itemDragStart="onDragStart($event, item)",
@click="onPotionClicked($event, item)"
)
template(slot="popoverContent", scope="ctx")
h4.popover-content-title {{ ctx.item.text() }}
.popover-content-text {{ ctx.item.notes() }}
template(slot="itemBadge", scope="ctx")
span.badge.badge-pill.badge-item.badge-quantity {{ quantity }}
.items(v-else)
item(
v-for="({data: item, quantity}, index) in items[group.key]",
v-if="group.open || index < itemsPerLine",
:item="item",
:key="item.key",
:itemContentClass="`${group.classPrefix}${item.key}`",
)
template(slot="popoverContent", scope="ctx")
h4.popover-content-title {{ ctx.item.text() }}
.popover-content-text {{ ctx.item.notes() }}
template(slot="itemBadge", scope="ctx")
@@ -55,9 +94,40 @@
@click="group.open = !group.open"
) {{ group.open ? $t('showLessItems', { type: $t(group.key) }) : $t('showAllItems', { type: $t(group.key), items: items[group.key].length }) }}
div.hatchingPotionInfo(ref="draggingPotionInfo")
div(v-if="currentDraggingPotion != null")
div.potion-icon(:class="'Pet_HatchingPotion_'+currentDraggingPotion.key")
div.popover
div.popover-content {{ $t('dragThisPotion', {potionName: currentDraggingPotion.text() }) }}
div.hatchingPotionInfo.mouse(ref="clickPotionInfo", v-if="potionClickMode")
div(v-if="currentDraggingPotion != null")
div.potion-icon(:class="'Pet_HatchingPotion_'+currentDraggingPotion.key")
div.popover
div.popover-content {{ $t('clickOnEggToHatch', {potionName: currentDraggingPotion.text() }) }}
</template>
<style lang="scss" scoped>
.hatchingPotionInfo {
position: absolute;
left: -500px;
&.mouse {
position: fixed;
pointer-events: none
}
.potion-icon {
margin: 0 auto;
}
.popover {
position: inherit;
width: 100px;
}
}
</style>
<script>
@@ -71,6 +141,9 @@ import Item from 'client/components/inventory/item';
const allowedSpecialItems = ['snowball', 'spookySparkles', 'shinySeed', 'seafoam'];
import DragDropDirective from 'client/directives/dragdrop.directive';
import MouseMoveDirective from 'client/directives/mouseposition.directive';
const groups = [
['eggs', 'Pet_Egg_'],
['hatchingPotions', 'Pet_HatchingPotion_'],
@@ -94,6 +167,10 @@ export default {
bDropdown,
bDropdownItem,
},
directives: {
drag: DragDropDirective,
mousePosition: MouseMoveDirective,
},
data () {
return {
itemsPerLine: 9,
@@ -101,6 +178,10 @@ export default {
searchTextThrottled: null,
groups,
sortBy: 'quantity', // or 'AZ'
currentDraggingPotion: null,
potionClickMode: false,
};
},
watch: {
@@ -150,5 +231,71 @@ export default {
return itemsByType;
},
},
methods: {
petExists (potionKey, eggKey) {
let animalKey = `${eggKey}-${potionKey}`;
let result = this.user.items.pets[animalKey] > 0;
return result;
},
hatchPet (potionKey, eggKey) {
this.$store.dispatch('common:hatch', {egg: eggKey, hatchingPotion: potionKey});
},
onDragEnd () {
this.currentDraggingPotion = null;
},
onDragStart ($event, potion) {
this.currentDraggingPotion = potion;
let itemRef = this.$refs.draggingPotionInfo;
let dragEvent = $event.event;
dragEvent.dataTransfer.setDragImage(itemRef, -20, -20);
},
onDragOver ($event, egg) {
let potionKey = this.currentDraggingPotion.key;
if (this.petExists(potionKey, egg.key)) {
$event.dropable = false;
}
},
onDrop ($event, egg) {
this.hatchPet(this.currentDraggingPotion.key, egg.key);
},
onDragLeave () {
},
onEggClicked ($event, egg) {
if (!this.petExists(this.currentDraggingPotion.key, egg.key)) {
this.hatchPet(this.currentDraggingPotion.key, egg.key);
}
this.currentDraggingPotion = null;
this.potionClickMode = false;
},
onPotionClicked ($event, potion) {
if (this.currentDraggingPotion === null || this.currentDraggingPotion !== potion) {
this.currentDraggingPotion = potion;
this.potionClickMode = true;
} else {
this.currentDraggingPotion = null;
this.potionClickMode = false;
}
},
mouseMoved ($event) {
if (this.potionClickMode) {
this.$refs.clickPotionInfo.style.left = `${$event.x + 20}px`;
this.$refs.clickPotionInfo.style.top = `${$event.y + 20}px`;
}
},
},
};
</script>

View File

@@ -7,15 +7,17 @@ b-popover(
h4.popover-content-title {{ item.text() }}
div.popover-content-text(v-html="item.notes()")
.item-wrapper
.item
.item-wrapper(@click="click($event)")
.item(:class="{'item-active': active }")
countBadge(
:show="true",
:count="itemCount"
)
span.item-content(
:class="'Pet_Food_'+item.key",
v-drag.food="item.key"
v-drag.food="item.key",
@itemDragEnd="dragend($event)",
@itemDragStart="dragstart($event)"
)
</template>
@@ -43,6 +45,20 @@ export default {
itemContentClass: {
type: String,
},
active: {
type: Boolean,
},
},
methods: {
dragend ($event) {
this.$emit('itemDragEnd', $event);
},
dragstart ($event) {
this.$emit('itemDragStart', $event);
},
click ($event) {
this.$emit('itemClick', $event);
},
},
};
</script>

View File

@@ -1,5 +1,5 @@
<template lang="pug">
.row.stable
.row.stable(v-mousePosition="30", @mouseMoved="mouseMoved($event)")
.standard-sidebar
div
b-popover(
@@ -21,22 +21,28 @@
.form-group
.form-check(
v-for="petGroup in petGroups",
v-if="viewOptions[petGroup.key].animalCount != 0",
:key="petGroup.key"
)
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox", v-model="viewOptions[petGroup.key].selected")
input.custom-control-input(
type="checkbox",
v-model="viewOptions[petGroup.key].selected",
:disabled="viewOptions[petGroup.key].animalCount == 0"
)
span.custom-control-indicator
span.custom-control-description(v-once) {{ petGroup.label }}
h3(v-once) {{ $t('mounts') }}
.form-group
.form-check(
v-for="mountGroup in mountGroups",
v-if="viewOptions[mountGroup.key].animalCount != 0",
:key="mountGroup.key"
)
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox", v-model="viewOptions[mountGroup.key].selected")
input.custom-control-input(
type="checkbox",
v-model="viewOptions[mountGroup.key].selected",
:disabled="viewOptions[mountGroup.key].animalCount == 0"
)
span.custom-control-indicator
span.custom-control-description(v-once) {{ mountGroup.label }}
@@ -79,8 +85,10 @@
v-for="pet in pets(petGroup, viewOptions[petGroup.key].open, hideMissing, selectedSortBy, searchTextThrottled, availableContentWidth)",
:key="pet.key",
v-drag.drop.food="pet.key",
@dragover="onDragOver($event, pet)",
@dropped="onDrop($event, pet)",
@itemDragOver="onDragOver($event, pet)",
@itemDropped="onDrop($event, pet)",
@itemDragLeave="onDragLeave()",
:class="{'last': pet.isLastInRow}"
)
petItem(
:item="pet",
@@ -88,20 +96,13 @@
:popoverPosition="'top'",
:progress="pet.progress",
:emptyItem="!pet.isOwned()",
:showPopover="pet.isOwned() || pet.isHatchable()",
@hatchPet="hatchPet",
:showPopover="pet.isOwned()",
:highlightBorder="highlightPet == pet.key",
@click="petClicked(pet)"
)
span(slot="popoverContent")
div(v-if="pet.isOwned()")
h4.popover-content-title {{ pet.name }}
div.hatchablePopover(v-else-if="pet.isHatchable()")
h4.popover-content-title {{ pet.name }}
div.popover-content-text(v-html="$t('haveHatchablePet', { potion: pet.potionName, egg: pet.eggName })")
div.potionEggGroup
div.potionEggBackground
div(:class="'Pet_HatchingPotion_'+pet.potionKey")
div.potionEggBackground
div(:class="'Pet_Egg_'+pet.eggKey")
template(slot="itemBadge", scope="ctx")
starBadge(
@@ -128,12 +129,14 @@
h4(v-if="viewOptions[mountGroup.key].animalCount != 0") {{ mountGroup.label }}
div.items
item(
mountItem(
v-for="mount in mounts(mountGroup, viewOptions[mountGroup.key].open, hideMissing, selectedSortBy, searchTextThrottled, availableContentWidth)",
:item="mount",
:itemContentClass="mount.isOwned() ? ('Mount_Icon_' + mount.key) : 'PixelPaw greyedOut'",
:itemContentClass="mount.isOwned() ? ('Mount_Icon_' + mount.key) : 'PixelPaw GreyedOut'",
:key="mount.key",
:popoverPosition="'top'"
:popoverPosition="'top'",
:emptyItem="!mount.isOwned()",
:showPopover="mount.isOwned()",
)
span(slot="popoverContent")
h4.popover-content-title {{ mount.name }}
@@ -168,13 +171,15 @@
) {{ drawerTabs[1].label }}
b-popover(
:triggers="['hover']",
:triggers="['click']",
:placement="'top'"
)
span(slot="content")
.popover-content-text Test Popover
.popover-content-text(v-html="$t('petLikeToEatText')", v-once)
div.float-right What does my pet like to eat?
div.float-right(v-once)
| {{ $t('petLikeToEat') + ' ' }}
span.svg-icon.inline.icon-16(v-html="icons.information")
drawer-slider(
@@ -188,6 +193,10 @@
foodItem(
:item="ctx.item",
:itemCount="userItems.food[ctx.item.key]",
:active="currentDraggingFood == ctx.item",
@itemDragEnd="onDragEnd()",
@itemDragStart="onDragStart($event, ctx.item)",
@itemClick="onFoodClicked($event, ctx.item)"
)
b-modal#welcome-modal(
@@ -201,12 +210,49 @@
h1.page-header(v-once) {{ $t('welcomeStable') }}
div.content-text(v-once) {{ $t('welcomeStableText') }}
b-modal#hatching-modal(
:visible="hatchablePet != null",
@change="resetHatchablePet($event)"
)
div.content(v-if="hatchablePet")
div.potionEggGroup
div.potionEggBackground
div(:class="'Pet_HatchingPotion_'+hatchablePet.potionKey")
div.potionEggBackground
div(:class="'Pet_Egg_'+hatchablePet.eggKey")
h4.title {{ hatchablePet.name }}
div.text(v-html="$t('haveHatchablePet', { potion: hatchablePet.potionName, egg: hatchablePet.eggName })")
span.svg-icon.icon-10(v-html="icons.close", slot="modal-header", @click="closeHatchPetDialog()")
div(slot="modal-footer")
button.btn.btn-primary(@click="hatchPet(hatchablePet)") {{ $t('hatch') }}
button.btn.btn-secondary.btn-flat(@click="closeHatchPetDialog()") {{ $t('cancel') }}
div.foodInfo(ref="dragginFoodInfo")
div(v-if="currentDraggingFood != null")
div.food-icon(:class="'Pet_Food_'+currentDraggingFood.key")
div.popover
div.popover-content {{ $t('dragThisFood', {foodName: currentDraggingFood.text() }) }}
div.foodInfo.mouse(ref="clickFoodInfo", v-if="foodClickMode")
div(v-if="currentDraggingFood != null")
div.food-icon(:class="'Pet_Food_'+currentDraggingFood.key")
div.popover
div.popover-content {{ $t('clickOnPetToFeed', {foodName: currentDraggingFood.text() }) }}
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
.standard-page .clearfix .float-right {
margin-right: 24px;
}
.inventory-item-container {
padding: 20px;
border: 1px solid;
@@ -234,10 +280,10 @@
display: inline-flex;
align-items: center;
width: 64px;
height: 64px;
border-radius: 2px;
background-color: #4e4a57;
width: 112px;
height: 112px;
border-radius: 4px;
background-color: #f9f9f9;
&:first-child {
margin-right: 24px;
@@ -266,21 +312,24 @@
.stable {
.standard-page {
flex: 1;
padding-right:0;
}
.drawer-container {
// 3% padding + 252px sidebar width
left: calc(3% + 252px) !important;
}
.svg-icon.inline.icon-16 {
vertical-align: bottom;
}
}
.drawer-slider .items {
height: 114px;
}
div#welcome-modal {
@mixin habitModal() {
display: flex;
justify-content: center;
flex-direction: column;
@@ -289,6 +338,14 @@
border: 0;
}
.modal-footer {
justify-content: center;
}
}
#welcome-modal {
@include habitModal();
.npc_matt {
margin: 0 auto 21px auto;
}
@@ -310,9 +367,43 @@
width: 400px;
}
}
.modal-footer {
justify-content: center;
#hatching-modal {
@include habitModal();
.content {
text-align: center;
margin: 9px;
width: 300px;
}
.title {
height: 24px;
margin-top: 24px;
font-family: Roboto;
font-size: 20px;
font-weight: bold;
font-stretch: condensed;
line-height: 1.2;
text-align: center;
color: #4e4a57;
}
.text {
height: 60px;
font-family: Roboto;
font-size: 14px;
line-height: 1.43;
text-align: center;
color: #686274;
}
span.svg-icon.icon-10 {
position: absolute;
right: 10px;
top: 10px;
}
}
@@ -320,6 +411,38 @@
background-color: $purple-50;
opacity: 0.9;
}
.last {
margin-right: 0 !important;
}
.no-focus:focus {
background-color: inherit;
color: inherit;
}
.popover-content-text {
margin-bottom: 0;
}
.foodInfo {
position: absolute;
left: -500px;
&.mouse {
position: fixed;
pointer-events: none
}
.food-icon {
margin: 0 auto;
}
.popover {
position: inherit;
width: 100px;
}
}
</style>
<script>
@@ -337,9 +460,11 @@
import _drop from 'lodash/drop';
import _flatMap from 'lodash/flatMap';
import _throttle from 'lodash/throttle';
import _last from 'lodash/last';
import Item from '../item';
import PetItem from './petItem';
import MountItem from './mountItem.vue';
import FoodItem from './foodItem';
import Drawer from 'client/components/inventory/drawer';
import toggleSwitch from 'client/components/ui/toggleSwitch';
@@ -349,6 +474,10 @@
import ResizeDirective from 'client/directives/resize.directive';
import DragDropDirective from 'client/directives/dragdrop.directive';
import MouseMoveDirective from 'client/directives/mouseposition.directive';
import svgInformation from 'assets/svg/information.svg';
import svgClose from 'assets/svg/close.svg';
// TODO Normalize special pets and mounts
// import Store from 'client/store';
@@ -360,6 +489,7 @@
PetItem,
Item,
FoodItem,
MountItem,
Drawer,
bDropdown,
bDropdownItem,
@@ -373,6 +503,7 @@
directives: {
resize: ResizeDirective,
drag: DragDropDirective,
mousePosition: MouseMoveDirective,
},
data () {
return {
@@ -391,6 +522,17 @@
'sortByHatchable',
],
icons: Object.freeze({
information: svgInformation,
close: svgClose,
}),
highlightPet: '',
hatchablePet: null,
foodClickMode: false,
currentDraggingFood: null,
selectedDrawerTab: 0,
availableContentWidth: 0,
};
@@ -443,7 +585,6 @@
petSource: {
special: this.content.specialPets,
},
alwaysHideMissing: true,
},
];
@@ -490,7 +631,6 @@
petSource: {
special: this.content.specialMounts,
},
alwaysHideMissing: true,
},
];
@@ -547,9 +687,15 @@
key: specialKey,
eggKey,
potionKey,
pet: this.content[`${type}Info`][specialKey].text(),
name: this.content[`${type}Info`][specialKey].text(),
isOwned () {
return [`${type}s`][this.key] > 0;
return userItems[`${type}s`][this.key] > 0;
},
mountOwned () {
return userItems.mounts[this.key] > 0;
},
isAllowedToFeed () {
return type === 'pet' && this.isOwned() && !this.mountOwned();
},
isHatchable () {
return false;
@@ -574,6 +720,12 @@
isOwned () {
return userItems[`${type}s`][animalKey] > 0;
},
mountOwned () {
return userItems.mounts[this.key] > 0;
},
isAllowedToFeed () {
return type === 'pet' && this.isOwned() && !this.mountOwned();
},
isHatchable () {
return userItems.eggs[egg.key] > 0 && userItems.hatchingPotions[potion.key] > 0;
},
@@ -594,7 +746,7 @@
let withProgress = isPetList && animalGroup.key !== 'specialPets';
// 1. Filter
if (hideMissing || animalGroup.alwaysHideMissing) {
if (hideMissing) {
animals = _filter(animals, (a) => {
return a.isOwned();
});
@@ -630,7 +782,7 @@
let animalRows = [];
let itemsPerRow = Math.floor(availableSpace / (94 + 24));
let itemsPerRow = Math.floor(availableSpace / (94 + 20));
let rowsToShow = isOpen ? Math.ceil(animals.length / itemsPerRow) : 1;
@@ -647,6 +799,11 @@
};
}) : row;
let lastRowItem = _last(rowWithProgressData);
if (lastRowItem) {
lastRowItem.isLastInRow = true;
}
animalRows.push(...rowWithProgressData);
}
@@ -679,6 +836,10 @@
return `Pet Pet-${pet.key}`;
}
if (pet.mountOwned()) {
return `GreyedOut Pet Pet-${pet.key}`;
}
if (pet.isHatchable()) {
return 'PixelPaw';
}
@@ -707,14 +868,80 @@
this.$store.dispatch('common:hatch', {egg: pet.eggKey, hatchingPotion: pet.potionKey});
},
onDragStart (ev, food) {
this.currentDraggingFood = food;
let itemRef = this.$refs.dragginFoodInfo;
let dragEvent = ev.event;
dragEvent.dataTransfer.setDragImage(itemRef, -20, -20);
},
onDragOver (ev, pet) {
if (this.userItems.mounts[pet.key]) {
if (!pet.isAllowedToFeed()) {
ev.dropable = false;
} else {
this.highlightPet = pet.key;
}
},
onDrop (ev, pet) {
this.$store.dispatch('common:feed', {pet: pet.key, food: ev.draggingKey});
this.highlightPet = '';
},
onDragEnd () {
this.currentDraggingFood = null;
this.highlightPet = '';
},
onDragLeave () {
this.highlightPet = '';
},
petClicked (pet) {
if (this.currentDraggingFood !== null && pet.isAllowedToFeed()) {
// food process
this.$store.dispatch('common:feed', {pet: pet.key, food: this.currentDraggingFood.key});
this.currentDraggingFood = null;
this.foodClickMode = false;
} else {
if (pet.isOwned() || !pet.isHatchable()) {
return;
}
// opens the hatch dialog
this.hatchablePet = pet;
}
},
closeHatchPetDialog () {
this.$root.$emit('hide::modal', 'hatching-modal');
},
resetHatchablePet ($event) {
if (!$event) {
this.hatchablePet = null;
}
},
onFoodClicked ($event, food) {
if (this.currentDraggingFood === null || this.currentDraggingFood !== food) {
this.currentDraggingFood = food;
this.foodClickMode = true;
} else {
this.currentDraggingFood = null;
this.foodClickMode = false;
}
},
mouseMoved ($event) {
if (this.foodClickMode) {
this.$refs.clickFoodInfo.style.left = `${$event.x + 20}px`;
this.$refs.clickFoodInfo.style.top = `${$event.y + 20}px`;
}
},
},
};

View File

@@ -0,0 +1,45 @@
<template lang="pug">
b-popover(
:triggers="[showPopover?'hover':'']",
:placement="popoverPosition",
)
span(slot="content")
slot(name="popoverContent", :item="item")
.item-wrapper
.item(
:class="{'item-empty': emptyItem}",
)
slot(name="itemBadge", :item="item")
span.item-content(:class="itemContentClass")
</template>
<script>
import bPopover from 'bootstrap-vue/lib/components/popover';
export default {
components: {
bPopover,
},
props: {
item: {
type: Object,
},
itemContentClass: {
type: String,
},
emptyItem: {
type: Boolean,
default: false,
},
popoverPosition: {
type: String,
default: 'bottom',
},
showPopover: {
type: Boolean,
default: true,
},
},
};
</script>

View File

@@ -6,19 +6,14 @@ b-popover(
span(slot="content")
slot(name="popoverContent", :item="item")
.item-wrapper
.item-wrapper(@click="click()")
.item(
:class="{'item-empty': emptyItem}",
@mouseup="holdStop",
@mouseleave="holdStop",
@mousedown.left="holdStart"
:class="{'item-empty': emptyItem, 'highlight': highlightBorder}",
)
slot(name="itemBadge", :item="item")
span.item-content(:class="itemContentClass")
span.pet-progress-background(v-if="progress > 0")
span.pet-progress-background(v-if="item.isAllowedToFeed() && progress > 0")
div.pet-progress-bar(v-bind:style="{width: 100 * progress/50 + '%' }")
span.pet-progress-background(v-if="holdProgress > 0")
div.pet-progress-bar.hold(v-bind:style="{width: 100 * holdProgress/5 + '%' }")
span.item-label(v-if="label") {{ label }}
</template>
@@ -36,15 +31,10 @@ b-popover(
height: 4px;
background-color: #24cc8f;
}
.pet-progress-bar.hold {
background-color: #54c3cc;
}
</style>
<script>
import bPopover from 'bootstrap-vue/lib/components/popover';
import {mapState} from 'client/libs/store';
export default {
components: {
@@ -68,6 +58,10 @@ b-popover(
type: Boolean,
default: false,
},
highlightBorder: {
type: Boolean,
default: false,
},
popoverPosition: {
type: String,
default: 'bottom',
@@ -77,40 +71,9 @@ b-popover(
default: true,
},
},
data () {
return {
holdProgress: -1,
};
},
computed: {
...mapState({
ATTRIBUTES: 'constants.ATTRIBUTES',
}),
},
methods: {
holdStart () {
let pet = this.item;
if (pet.isOwned() || !pet.isHatchable()) {
return;
}
this.holdProgress = 1;
this.currentHoldingTimer = setInterval(() => {
if (this.holdProgress === 5) {
this.holdStop();
this.$emit('hatchPet', pet);
}
this.holdProgress += 1;
}, 1000);
},
holdStop () {
if (this.currentHoldingTimer) {
clearInterval(this.currentHoldingTimer);
this.holdProgress = -1;
}
click () {
this.$emit('click', {});
},
},
};

View File

@@ -0,0 +1,218 @@
<template lang="pug">
b-modal#inbox-modal(title="", :hide-footer="true", size='lg')
.header-wrap.container(slot="modal-header")
.row
.col-4
.row
.col-2
.svg-icon.envelope(v-html="icons.messageIcon")
.col-6
h2.text-center(v-once) {{$t('messages')}}
// @TODO: Implement this after we fix username bug
// .col-2.offset-1
// button.btn.btn-secondary(@click='toggleClick()') +
// .col-8.to-form(v-if='displayCreate')
// strong To:
// b-form-input
.row
.col-4.sidebar
.search-section
b-form-input(:placeholder="$t('search')", v-model='search')
.empty-messages.text-center(v-if='filtersConversations.length === 0')
.svg-icon.envelope(v-html="icons.messageIcon")
h4(v-once) {{$t('emptyMessagesLine1')}}
p(v-once) {{$t('emptyMessagesLine2')}}
.conversations(v-if='filtersConversations.length > 0')
.conversation(v-for='conversation in conversations', @click='selectConversation(conversation.key)', :class="{active: selectedConversation === conversation.key}")
div
span {{conversation.name}}
span.timeago {{conversation.date}}
div {{conversation.lastMessageText}}
.col-8.messages
.message(v-for='message in currentMessages') {{message.text}}
// @TODO: Implement new message header here when we fix the above
.new-message-row(v-if='selectedConversation')
b-form-input(v-model='newMessage')
button.btn.btn-secondary(@click='sendPrivateMessage()') Send
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
.envelope {
color: $gray-400 !important;
margin-top: 1em;
}
h2 {
margin-top: .5em;
}
.sidebar {
background-color: $gray-700;
min-height: 600px;
padding: 0;
.search-section {
padding: 1em;
box-shadow: 0 1px 2px 0 rgba(26, 24, 29, 0.24);
}
}
.messages {
position: relative;
padding-left: 0;
}
.to-form input {
width: 60%;
display: inline-block;
margin-left: 1em;
}
.empty-messages {
margin-top: 10em;
color: $gray-400;
padding: 1em;
h4 {
color: $gray-400;
margin-top: 1em;
}
.envelope {
width: 30px;
margin: 0 auto;
}
}
.new-message-row {
background-color: $gray-700;
position: absolute;
bottom: 0;
height: 88px;
width: 100%;
padding: 1em;
input {
display: inline-block;
width: 80%;
}
button {
box-shadow: none;
margin-left: 1em;
}
}
.conversation {
padding: 1.5em;
background: $white;
height: 80px;
.timeago {
margin-left: 1em;
}
}
.conversation.active {
border: 1px solid $purple-400;
}
.conversation:hover {
cursor: pointer;
}
</style>
<script>
import moment from 'moment';
import filter from 'lodash/filter';
import { mapState } from 'client/libs/store';
import bModal from 'bootstrap-vue/lib/components/modal';
import bFormInput from 'bootstrap-vue/lib/components/form-input';
import messageIcon from 'assets/svg/message.svg';
export default {
components: {
bModal,
bFormInput,
},
data () {
return {
icons: Object.freeze({
messageIcon,
}),
displayCreate: true,
selectedConversation: '',
search: '',
newMessage: '',
};
},
computed: {
...mapState({user: 'user.data'}),
conversations () {
let conversations = {};
for (let messageId in this.user.inbox.messages) {
let message = this.user.inbox.messages[messageId];
let userId = message.uuid;
if (!this.selectedConversation) this.selectedConversation = userId;
if (!conversations[userId]) {
conversations[userId] = {
name: message.user,
key: userId,
messages: [],
};
}
conversations[userId].messages.push({
text: message.text,
timestamp: message.timestamp,
});
conversations[userId].lastMessageText = message.text;
conversations[userId].date = moment(new Date(message.timestamp)).fromNow();
}
return conversations;
},
currentMessages () {
if (!this.selectedConversation) return;
return this.conversations[this.selectedConversation].messages;
},
filtersConversations () {
if (!this.search) return Object.values(this.conversations);
return filter(this.conversations, (conversation) => {
return conversation.name.toLowerCase().indexOf(this.search.toLowerCase()) !== -1;
});
},
},
methods: {
toggleClick () {
this.displayCreate = !this.displayCreate;
},
selectConversation (key) {
this.selectedConversation = key;
},
sendPrivateMessage () {
this.$store.dispatch('members:sendPrivateMessage', {
toUserId: this.selectedConversation,
message: this.newMessage,
});
this.conversations[this.selectedConversation].messages.push({
text: this.newMessage,
timestamp: new Date(),
});
this.conversations[this.selectedConversation].lastMessageText = this.newMessage;
this.conversations[this.selectedConversation].date = new Date();
this.newMessage = '';
},
},
};
</script>

View File

@@ -7,12 +7,15 @@ import _without from 'lodash/without';
* DRAG_GROUP is a static custom value
* KEY_OF_ITEM
*
* v-drag.DRAG_GROUP="KEY_OF_ITEM"
* v-drag.drop.DRAG_GROUP="KEY_OF_ITEM" @dropped="callback" @dragover="optional"
* v-drag.DRAG_GROUP="KEY_OF_ITEM" @itemDragEnd="optional" @itemDragStart="optional"
* v-drag.drop.DRAG_GROUP="KEY_OF_ITEM" @itemDropped="callback" @itemDragOver="optional"
*/
const DROPPED_EVENT_NAME = 'dropped';
const DRAGOVER_EVENT_NAME = 'dragover';
const DROPPED_EVENT_NAME = 'itemDropped';
const DRAGSTART_EVENT_NAME = 'itemDragStart';
const DRAGEND_EVENT_NAME = 'itemDragEnd';
const DRAGOVER_EVENT_NAME = 'itemDragOver';
const DRAGLEAVE_EVENT_NAME = 'itemDragLeave';
export default {
bind (el, binding, vnode) {
@@ -24,13 +27,28 @@ export default {
el.draggable = true;
el.handleDrag = (ev) => {
ev.dataTransfer.setData('KEY', binding.value);
let dragStartEventData = {
event: ev,
};
emit(vnode, DRAGSTART_EVENT_NAME, dragStartEventData);
};
el.addEventListener('dragstart', el.handleDrag);
el.handleDragEnd = () => {
let dragEndEventData = {};
emit(vnode, DRAGEND_EVENT_NAME, dragEndEventData);
};
el.addEventListener('dragend', el.handleDrag);
} else {
el.handleDragOver = (ev) => {
let dragOverEventData = {
dropable: true,
draggingKey: ev.dataTransfer.getData('KEY'),
event: ev,
};
emit(vnode, DRAGOVER_EVENT_NAME, dragOverEventData);
@@ -47,16 +65,23 @@ export default {
emit(vnode, DROPPED_EVENT_NAME, dropEventData);
};
el.handleDragLeave = () => {
emit(vnode, DRAGLEAVE_EVENT_NAME, {});
};
el.addEventListener('dragover', el.handleDragOver);
el.addEventListener('dragleave', el.handleDragLeave);
el.addEventListener('drop', el.handleDrop);
}
},
unbind (el) {
if (!el.isDropHandler) {
el.removeEventListener('drag', el.handleDrag);
el.removeEventListener('dragstart', el.handleDrag);
el.removeEventListener('dragend', el.handleDragEnd);
} else {
el.removeEventListener('dragover', el.handleDragOver);
el.removeEventListener('dragleave', el.handleDragLeave);
el.removeEventListener('drop', el.handleDrop);
}
},

View File

@@ -0,0 +1,31 @@
import Vue from 'vue';
import _throttle from 'lodash/throttle';
import { emit } from './directive.common';
/**
* v-mousePosition="throttleTimeout", @mouseMoved="callback()"
*/
const EVENT_NAME = 'mouseMoved';
export default {
bind (el, binding, vnode) {
el.handleMouseMove = _throttle((ev) => {
emit(vnode, EVENT_NAME, {
x: ev.clientX,
y: ev.clientY,
});
}, binding.value);
window.addEventListener('mousemove', el.handleMouseMove);
// send the first width
Vue.nextTick(el.handleWindowResize);
},
unbind (el) {
window.removeEventListener('mousemove', el.handleMouseMove);
},
};

View File

@@ -12,6 +12,9 @@ import Page from './components/page';
const Home = () => import(/* webpackChunkName: "static" */'./components/static/home');
const RegisterLogin = () => import(/* webpackChunkName: "auth" */'./components/auth/registerLogin');
const CreatorIntro = () => import(/* webpackChunkName: "creator" */'./components/creatorIntro');
// Except for tasks that are always loaded all the other main level
// All the main level
// components are loaded in separate webpack chunks.
// See https://webpack.js.org/guides/code-splitting-async/
@@ -32,7 +35,7 @@ const InboxConversationPage = () => import(/* webpackChunkName: "inbox" */ './co
// Guilds
const GuildIndex = () => import(/* webpackChunkName: "guilds" */ './components/guilds/index');
// const TavernPage = () => import(/* webpackChunkName: "guilds" */ './components/guilds/tavern');
const TavernPage = () => import(/* webpackChunkName: "guilds" */ './components/guilds/tavern');
const MyGuilds = () => import(/* webpackChunkName: "guilds" */ './components/guilds/myGuilds');
const GuildsDiscoveryPage = () => import(/* webpackChunkName: "guilds" */ './components/guilds/discovery');
const GuildPage = () => import(/* webpackChunkName: "guilds" */ './components/guilds/guild');
@@ -50,6 +53,7 @@ const router = new VueRouter({
},
// requiresLogin is true by default, isStatic false
routes: [
{ name: 'creator', path: '/creator', component: CreatorIntro },
{ name: 'home', path: '/home', component: Home, meta: {requiresLogin: false} },
{ name: 'register', path: '/register', component: RegisterLogin, meta: {requiresLogin: false} },
{ name: 'login', path: '/login', component: RegisterLogin, meta: {requiresLogin: false} },
@@ -69,7 +73,7 @@ const router = new VueRouter({
path: '/guilds',
component: GuildIndex,
children: [
{ name: 'tavern', path: 'tavern', component: GuildPage },
{ name: 'tavern', path: 'tavern', component: TavernPage },
{
name: 'myGuilds',
path: 'myGuilds',

View File

@@ -25,4 +25,8 @@ export function set (store, changes) {
// TODO
// .then((res) => console.log('set', res))
// .catch((err) => console.error('set', err));
}
}
export function sleep () {
// @TODO: Implemented
}

View File

@@ -54,6 +54,7 @@
"user": "User",
"market": "Market",
"groupPlansTitle": "Group Plans",
"newGroupTitle": "New Group",
"subscriberItem": "Mystery Item",
"newSubscriberItem": "New Mystery Item",
"subscriberItemText": "Each month, subscribers will receive a mystery item. This is usually released about one week before the end of the month. See the wiki's 'Mystery Item' page for more information.",

View File

@@ -229,7 +229,7 @@
"yourTaskHasBeenApproved": "Your task \"<%= taskText %>\" has been approved",
"userHasRequestedTaskApproval": "<%= user %> has requested task approval for <%= taskName %>",
"approve": "Approve",
"approvalTitle": "<%= text %> for user: <%= userName %>",
"approvalTitle": "<%= userName %> has completed <%= type %>: \"<%= text %>\"",
"confirmTaskApproval": "Do you want to reward <%= username %> for completing this task?",
"groupSubscriptionPrice": "$9 every month + $3 a month for every additional group member",
"groupAdditionalUserCost": " +$3.00/month/user",
@@ -251,7 +251,18 @@
"groupBenefitSevenTitle": "Get a brand-new exclusive Jackalope Mount",
"groupBenefitEightTitle": "Add Group Managers to help manage tasks",
"groupBenefitEightDescription": "Want to share your group's responsibilities? Promote people to Group Managers to help the Leader add, assign, and approve tasks!",
"groupBenefitMessageLimitTitle": "Increase message limit",
"groupBenefitMessageLimitDescription": "Your message limit is doubled to house up to 400 messages at a time!",
"teamBasedTasks": "Team-based Tasks",
"specializedCommunication": "Specialized Communication",
"funExtras": "Fun Extras",
"enterprisePlansButton": "Ask about Enterprise Plans",
"enterprisePlansDescription": "Looking for a larger install with custom needs? See if our enterprise plans are right for you.",
"enterprisePlansEmailSubject": "Question regarding Enterprise Plans",
"familyPlansButton": "Sign Up for Family Plan Mailing List",
"familyPlansDescription": "Want a cozier solution to manage your household? Family Plans are coming soon!",
"createAGroup": "Create a Group",
"getAGroupPlanToday": "Get a Group Plan Today",
"assignFieldPlaceholder": "Type a group member's profile name",
"cannotDeleteActiveGroup": "You cannot remove a group with an active subscription",
"groupTasksTitle": "Group Tasks List",
@@ -286,5 +297,7 @@
"leaderMarker": " - Leader",
"managerMarker": " - Manager",
"joinedGuild": "Joined a Guild",
"joinedGuildText": "Ventured into the social side of Habitica by joining a Guild!"
"joinedGuildText": "Ventured into the social side of Habitica by joining a Guild!",
"badAmountOfGemsToPurchase": "Amount must be at least 1.",
"groupPolicyCannotGetGems": "The policy of one group you're part of prevents its members from obtaining gems."
}

View File

@@ -35,14 +35,17 @@
"groupBy2": "Group By",
"quantity": "Quantity",
"AZ": "A-Z",
"costumeDisabled": "You have disabled your costume.",
"filterByStandard": "Standard",
"filterByMagicPotion": "Magin Potion",
"filterByMagicPotion": "Magic Potion",
"filterByQuest": "Quest",
"standard": "Standard",
"sortByColor": "Color",
"sortByHatchable": "Hatchable",
"haveHatchablePet": "You have a <%= potion %> and <%= egg %> to hatch this pet! <b>Click and hold</b> the paw print to hatch.",
"hatch": "Hatch!",
"dragThisFood": "Drag this <%= foodName %> to a Pet and watch it grow!",
"clickOnPetToFeed": "Click on a Pet to feed <%= foodName %> and watch it grow!",
"dragThisPotion": "Drag this <%= potionName %> to an Egg and hatch a new pet!",
"clickOnEggToHatch": "Click on an Egg to use your <%= potionName %> and hatch a new pet!",
"editAvatar": "Edit Avatar",
"sort": "Sort",
"memberCount": "Member Count",
@@ -127,5 +130,58 @@
"noFoodAvailable": "You don't have any food.",
"gotIt": "Got it!",
"welcomeStable": "Welcome to the Stable!",
"welcomeStableText": "I'm Matt, the Beast Master. Starting at level 3, you can hatch Pets from Eggs by using Potions you find! When you hatch a Pet from your Inventory, it will appear here! Click a Pet's to add it to your avatar. Feed them with the Food you find in your Inventory after level 3, and they'll grow into hardy Mounts."
"welcomeStableText": "I'm Matt, the Beast Master. Starting at level 3, you can hatch Pets from Eggs by using Potions you find! When you hatch a Pet from your Inventory, it will appear here! Click a Pet's to add it to your avatar. Feed them with the Food you find in your Inventory after level 3, and they'll grow into hardy Mounts.",
"excercise": "Excercise",
"creativity": "Creativity",
"budgeting": "Budgeting",
"petLikeToEatText": "Pets will grow no matter what you feed them, but they'll grow faster if you feed them the one food that they like best. Experiment to find out the pattern, or see the answers here: <br/> <a href=\"http://habitica.wikia.com/wiki/Food_Preferences\" target=\"_blank\">http://habitica.wikia.com/wiki/Food_Preferences</a>",
"petLikeToEat": "What does my pet like to eat?",
"welcomeTo": "Welcome to",
"justinIntroMessage1": "Hello there! You must be new here. My name is Justin, Ill be your guide in Habitica.",
"justinIntroMessage2": "To start, youll need to create an avatar.",
"justinIntroMessage3": "Great! Now, what are you interested in working on throughout this journey?",
"prev": "Prev",
"next": "Next",
"randomize": "Randomize",
"skin": "Skin",
"hair": "Hair",
"extra": "Extra",
"size": "Size",
"shirt": "Shirt",
"bangs": "Bangs",
"ponytail": "Ponytail",
"glasses": "Glasses",
"welcomeToTavern": "Welcome to The Tavern!",
"communityGuidelinesIntro": "Habitica tries to create a welcoming environment for users of all ages and backgrounds, especially in public spaces like the Tavern. If you have any questions, please consult our Community Guidelines.",
"acceptCommunityGuidelines": "I agree to follow the Community Guidelines",
"sleepDescription": "Need a break? Check into Daniels Inn to pause some of Habiticas more difficult game mechanics:",
"sleepBullet1": "Missed Dailies wont damage you",
"sleepBullet2": "Tasks wont lose streaks or decay in color",
"sleepBullet3": "Bosses wont do damage for your missed Dailies",
"sleepBullet4": "Your boss damage or collection Quest items will stay pending until check-out",
"pauseDailies": "Pause Damage",
"unpauseDailies": "Unpause Damage",
"staffAndModerators": "Staff and Moderators",
"helpfulLinks": "Helpful Links",
"communityGuidelinesLink": "Community Guidelines",
"lookingForGroup": "Looking for Group (Party Wanted) Posts",
"faq": "FAQ",
"dataDisplayTool": "Data Display Tool",
"reportProblem": "Report a Problem",
"requestFeature": "Request a Feature",
"askQuestionGuild": "Ask a Question (Habitica Help guild)",
"playerTiersDesc": "The colored usernames you see in chat represent a persons contributor tier. The higher the tier, the more the person has contributed to habitica through art, code, the community, or more!",
"tier1": "Tier 1 (Friend)",
"tier2": "Tier 2 (Friend)",
"tier3": "Tier 3 (Elite)",
"tier4": "Tier 4 (Elite)",
"tier5": "Tier 5 (Champion)",
"tier6": "Tier 6 (Champion)",
"tier7": "Tier 7 (Legendary)",
"tierModerator": "Moderator (Guardian)",
"tierStaff": "Staff (Heroic)",
"tierNPC": "NPC",
"messages": "Messages",
"emptyMessagesLine1": "You dont have any messages",
"emptyMessagesLine2": "Send a message to start a conversation!"
}

View File

@@ -71,6 +71,7 @@
"displayNow": "Display Now",
"displayLater": "Display Later",
"petNotOwned": "You do not own this pet.",
"mountNotOwned": "You do not own this mount.",
"earnedCompanion": "With all your productivity, you've earned a new companion. Feed it to make it grow!",
"feedPet": "Feed <%= article %><%= text %> to your <%= name %>?",
"useSaddle": "Saddle <%= pet %>?",

View File

@@ -439,7 +439,7 @@
"questTaskwoodsTerror3DropWeapon": "Taskwoods Lantern (Two-Handed Weapon)",
"questFerretText": "The Nefarious Ferret",
"questFerretNotes": "Walking through Habit City, you see an unhappy crowd surrounding a red-robed Ferret.<br><br>\"That productivity potion you sold me is useless!\" @Beffymaroo complains. \"I watched three hours of TV last night instead of doing my chores!\"<br><br>\"Yeah!\" shouts @Pandah. \"And today I spent an hour rearranging my books instead of reading them!\"<br><br>The Nefarious Ferret spreads his hands innocently. \"That's more TV watching and book organizing than you'd normally get done, isnt it?\"<br><br>The crowd erupts in anger.<br><br>\"No refunds!\" crows the Nefarious Ferret. He fires a bolt of magic into the crowd, preparing to escape in the smoke.<br><br>\"Please, Habitican!\" @Faye says, grabbing your arm. \"Defeat the ferret and make him refund his dishonest earnings!\"",
"questFerretNotes": "Walking through Habit City, you see an unhappy crowd surrounding a red-robed Ferret.<br><br>\"That productivity potion you sold me is useless!\" @Beffymaroo complains. \"I watched three hours of TV last night instead of doing my chores!\"<br><br>\"Yeah!\" shouts @Pandah. \"And today I spent an hour rearranging my books instead of reading them!\"<br><br>The Nefarious Ferret spreads his hands innocently. \"That's more TV watching and book organizing than you'd normally get done, isn't it?\"<br><br>The crowd erupts in anger.<br><br>\"No refunds!\" crows the Nefarious Ferret. He fires a bolt of magic into the crowd, preparing to escape in the smoke.<br><br>\"Please, Habitican!\" @Faye says, grabbing your arm. \"Defeat the ferret and make him refund his dishonest earnings!\"",
"questFerretCompletion": "You defeat the soft-furred swindler and @UncommonCriminal gives the crowd their refunds. There's even a little gold left over for you. Plus, it looks like the Nefarious Ferret dropped some eggs in his hurry to get away!",
"questFerretBoss": "Nefarious Ferret",
"questFerretDropFerretEgg": "Ferret (Egg)",
@@ -494,7 +494,7 @@
"questStoikalmCalamity1DropArmor": "Mammoth Rider Armor",
"questStoikalmCalamity2Text": "Stoïkalm Calamity, Part 2: Seek the Icicle Caverns",
"questStoikalmCalamity2Notes": "The stately hall of the Mammoth Riders is an austere masterpiece of architecture, but it is also entirely empty. There's no furniture, the weapons are missing, and even the columns were picked clean of their inlays.<br><br>\"Those skulls scoured the place,\" Lady Glaciate says, and there is a blizzard brewing in her tone. \"Humiliating. Not a soul is to mention this to the April Fool, or I will never hear the end of it.\"<br><br>\"How mysterious!\" says @Beffymaroo. \"But where did they--\"<br><br>\"The icicle drake caverns.\" Lady Glaciate gestures at shining coins spilled in the snow outside. \"Sloppy.\"<br><br>\"But aren't icicle drakes honorable creatures with their own treasure hoards?\" @Beffymaroo asks. \"Why would they possibly--\"<br><br>\"Mind control,\" says Lady Glaciate, utterly unphased. \"Or something equally melodramatic and inconvenient.\" She begins to stride from the hall. \"Why are you just standing there?\"<br><br>Quickly, go follow the trail of Icicle Coins!",
"questStoikalmCalamity2Notes": "The stately hall of the Mammoth Riders is an austere masterpiece of architecture, but it is also entirely empty. There's no furniture, the weapons are missing, and even the columns were picked clean of their inlays.<br><br>\"Those skulls scoured the place,\" Lady Glaciate says, and there is a blizzard brewing in her tone. \"Humiliating. Not a soul is to mention this to the April Fool, or I will never hear the end of it.\"<br><br>\"How mysterious!\" says @Beffymaroo. \"But where did they--\"<br><br>\"The icicle drake caverns.\" Lady Glaciate gestures at shining coins spilled in the snow outside. \"Sloppy.\"<br><br>\"But aren't icicle drakes honorable creatures with their own treasure hoards?\" @Beffymaroo asks. \"Why would they possibly--\"<br><br>\"Mind control,\" says Lady Glaciate, utterly unfazed. \"Or something equally melodramatic and inconvenient.\" She begins to stride from the hall. \"Why are you just standing there?\"<br><br>Quickly, go follow the trail of Icicle Coins!",
"questStoikalmCalamity2Completion": "The Icicle Coins lead you straight to the buried entrance of a cleverly hidden cavern. Though the weather outside is calm and lovely, with the sunlight sparkling across the expanse of snow, there is a howling within like a fierce winter wind. Lady Glaciate grimaces and hands you a Mammoth Rider helm. \"Wear this,\" she says. \"You'll need it.\"",
"questStoikalmCalamity2CollectIcicleCoins": "Icicle Coins",
"questStoikalmCalamity2DropHeadgear": "Mammoth Rider Helm (Headgear)",

View File

@@ -23,7 +23,7 @@
"giftSubscriptionText4": "Thanks for supporting Habitica!",
"monthUSD": "USD / Month",
"organization": "Organization",
"groupPlans": "Corporate Plans",
"groupPlans": "Group Plans",
"indivPlan1": "For individuals, Habitica is free to play. Even for small interest groups, free (or cheap)",
"indivPlan2": "can be used to motivate participants in behavioral modification. Think writing groups, art challenges, and more.",
"groupText1": "But some group leaders will want more control, privacy, security, and support. Examples of such groups are families, health and wellness groups, employee groups, and more. These plans provide private instances of Habitica for your group or organization, secure and independent of",

View File

@@ -147,6 +147,10 @@
"taskApprovalHasBeenRequested": "Approval has been requested",
"approvals": "Approvals",
"approvalRequired": "Approval Required",
"repeatZero": "Daily is never due",
"repeatType": "Repeat Type",
"repeatTypeHelpTitle": "What kind of repeat is this?",
"repeatTypeHelp": "Select \"Daily\" if you want this task to repeat every day or every third day, etc. Select \"Weekly\"if you want it to repeat on certain days of the week. If you select \"Monthly\" or \"Yearly\", adjust the Start Date to control which day of the month or year the task will be due on.",
"weekly": "Weekly",
"monthly": "Monthly",
"yearly": "Yearly",

View File

@@ -253,34 +253,34 @@
"backgroundMagicBeanstalkNotes": "Mendaki Pohon Kacang Sihir.",
"backgroundMeanderingCaveText": "Gua Berkelok-kelok",
"backgroundMeanderingCaveNotes": "Jelajahi Gua Berkelok-kelok.",
"backgroundMistiflyingCircusText": "Mistiflying Circus",
"backgroundMistiflyingCircusNotes": "Carouse in the Mistiflying Circus.",
"backgrounds042017": "SET 35: Released April 2017",
"backgroundBugCoveredLogText": "Bug-Covered Log",
"backgroundBugCoveredLogNotes": "Investigate a Bug-Covered Log.",
"backgroundGiantBirdhouseText": "Giant Birdhouse",
"backgroundGiantBirdhouseNotes": "Perch in a Giant Birdhouse.",
"backgroundMistShroudedMountainText": "Mist-Shrouded Mountain",
"backgroundMistShroudedMountainNotes": "Summit a Mist-Shrouded Mountain.",
"backgrounds052017": "SET 36: Released May 2017",
"backgroundGuardianStatuesText": "Guardian Statues",
"backgroundGuardianStatuesNotes": "Stand vigil in front of Guardian Statues.",
"backgroundHabitCityStreetsText": "Habit City Streets",
"backgroundHabitCityStreetsNotes": "Explore the Streets of Habit City.",
"backgroundOnATreeBranchText": "On a Tree Branch",
"backgroundOnATreeBranchNotes": "Perch On a Tree Branch.",
"backgrounds062017": "SET 37: Released June 2017",
"backgroundBuriedTreasureText": "Buried Treasure",
"backgroundBuriedTreasureNotes": "Unearth Buried Treasure.",
"backgroundOceanSunriseText": "Ocean Sunrise",
"backgroundOceanSunriseNotes": "Admire an Ocean Sunrise.",
"backgroundSandcastleText": "Sandcastle",
"backgroundSandcastleNotes": "Rule over a Sandcastle.",
"backgrounds072017": "SET 38: Released July 2017",
"backgroundGiantSeashellText": "Giant Seashell",
"backgroundGiantSeashellNotes": "Lounge in a Giant Seashell.",
"backgroundKelpForestText": "Kelp Forest",
"backgroundKelpForestNotes": "Swim through a Kelp Forest.",
"backgroundMidnightLakeText": "Midnight Lake",
"backgroundMidnightLakeNotes": "Rest by a Midnight Lake."
"backgroundMistiflyingCircusText": "Sirkus Mistiflying",
"backgroundMistiflyingCircusNotes": "Bersenang-senang di Sirkus Mistiflying",
"backgrounds042017": "SET 35: Dirilis April 2017",
"backgroundBugCoveredLogText": "Batang Pohon Berserubung Serangga",
"backgroundBugCoveredLogNotes": "Investigasi Batang Pohon Berserubung Serangga",
"backgroundGiantBirdhouseText": "Rumah Burung Raksasa",
"backgroundGiantBirdhouseNotes": "Bertengker di Rumah Burung Raksasa",
"backgroundMistShroudedMountainText": "Pegunungan Berserabut Kabut",
"backgroundMistShroudedMountainNotes": "Mendaki Pegunungan Berserabut Kabut",
"backgrounds052017": "SET 36: Dirilis Mei 2017",
"backgroundGuardianStatuesText": "Patung Penunggu",
"backgroundGuardianStatuesNotes": "Berjaga-jaga di depan Patung Penunggu",
"backgroundHabitCityStreetsText": "Jalanan Kota Habit",
"backgroundHabitCityStreetsNotes": "Bereksplorasi di Jalanan Kota Habit",
"backgroundOnATreeBranchText": "Di Ranting Pohon",
"backgroundOnATreeBranchNotes": "Bertengker di Ranting Pohon",
"backgrounds062017": "SET 37: Dirilis Juni 2017",
"backgroundBuriedTreasureText": "Harta Karun Tersembunyi",
"backgroundBuriedTreasureNotes": "Galilah Harta Karun Tersembunyi",
"backgroundOceanSunriseText": "Matahari Terbit di Tepi Lautan",
"backgroundOceanSunriseNotes": "Mengagumi Matahari Terbit di Tepi Lautan",
"backgroundSandcastleText": "Istana Pasir",
"backgroundSandcastleNotes": "Memerintah Sebuah Istana Pasir",
"backgrounds072017": "SET 38: Dirilis Juli 2017",
"backgroundGiantSeashellText": "Kerang Raksasa",
"backgroundGiantSeashellNotes": "Bermalas-malas di Kerang Raksasa",
"backgroundKelpForestText": "Hutan Rumput Laut",
"backgroundKelpForestNotes": "Berenang di Hutan Rumput Laut",
"backgroundMidnightLakeText": "Danau Tengah Malam",
"backgroundMidnightLakeNotes": "Beristirahat di Danau Tengah Malam"
}

View File

@@ -80,6 +80,6 @@
"userAlreadyInChallenge": "Pengguna telah berpartisipasi di dalam tantangan ini.",
"cantOnlyUnlinkChalTask": "Hanya tugas tantangan rusak yang dapat diputuskan.",
"shortNameTooShort": "Nama Label harus setidaknya memiliki 3 karakter.",
"joinedChallenge": "Joined a Challenge",
"joinedChallengeText": "This user put themselves to the test by joining a Challenge!"
"joinedChallenge": "Bergabung dengan sebuah Tantangan",
"joinedChallengeText": "User ini telah mengetes dirinya dengan bergabung pada sebuah Tantangan!"
}

View File

@@ -1,5 +1,5 @@
{
"communityGuidelinesWarning": "Please keep in mind that your Display Name, profile photo, and blurb must comply with the <a href='https://habitica.com/static/community-guidelines' target='_blank'>Community Guidelines</a> (e.g. no profanity, no adult topics, no insults, etc). If you have any questions about whether or not something is appropriate, feel free to email <%= hrefBlankCommunityManagerEmail %>!",
"communityGuidelinesWarning": "Ingatlah bahwa Nama Tampilan, foto profil, dan celotehmu harus mengikuti <a href='https://habitica.com/static/community-guidelines' target='_blank'>Pedoman Komunitas</a> (misalnya, tidak menggunakan bahasa senonoh, topik dewasa, penghinaan, dsb). Jika kamu mempunyai pertanyaan apapun mengenai pantasnya sesuatu hal, silahkan email <%= hrefBlankCommunityManagerEmail %>!",
"profile": "Profil",
"avatar": "Ubah Tampilan Avatar",
"other": "Lainnya",

View File

@@ -12,7 +12,7 @@
"commGuideList01B": "<strong>Tabiat yang Rajin.</strong> Habiticans bekerja keras untuk memperbaiki hidup mereka, juga untuk membangun dan memperbaiki situs ini secara terus-menerus. Kami adalah proyek open-source, yang berarti kami sebisa mungkin selalu berusaha untuk membuat situs ini menjadi tempat terbaik.",
"commGuideList01C": "<strong>Perilaku yang Suportif.</strong> Habiticans senang jika orang lain senang dan saling menghibur pada saat-saat yang sulit. Kita saling menguatkan, bergantung pada satu sama lain, dan belajar pada satu sama lain. Dalam kelompok, kita melakukan hal ini melalui mantera; di ruang chat, kita melakukan ini dengan kata-kata yang ramah dan suportif.",
"commGuideList01D": "<strong>Sikap Saling Menghargai.</strong> Kita berasal dari latar belakang yang berbeda, juga memiliki keahlian dan opini yang berbeda. Itulah yang membuat komunitas kita sangat menakjubkan. Habiticans menghargai perbedaan ini, bahkan merayakannya. Jangan pergi ke mana-mana, dan kamu akan segera mendapatkan teman-teman baru dari berbagai lapisan komunitas.",
"commGuideHeadingMeet": "Meet the Staff and Mods!",
"commGuideHeadingMeet": "Temuilah para Staff dan Moderator!",
"commGuidePara006": "Habitica memiliki beberapa ksatria yang bersatu padu dengan anggota staff untuk menjaga komunitas ini tetap damai, puas, dan bebas dari pengganggu. Masing-masing memiliki domain yang spesifik, namun sewaktu-waktu dapat dipanggil untuk membantu di lingkup sosial yang lain. Staff dan Moderator sering kali memulai pernyataan resmi dengan kata-kata \"Mod Talk\" atau \"Mod Hat On\"",
"commGuidePara007": "Staf punya warna ungu dengan tanda mahkota. Gelar mereka adalah \"Pahlawan\".",
"commGuidePara008": "Moderator memiliki tag biru gelap dengan tanda bintang. Gelar mereka adalah \"Pengawal\", kecuali Bailey, yang merupakan NPC dan memiliki tag hitam-hijau dengan tanda bintang.",

View File

@@ -152,8 +152,8 @@
"questEggButterflyText": "Ulat",
"questEggButterflyMountText": "Kupu-kupu",
"questEggButterflyAdjective": "a cute",
"questEggNudibranchText": "Nudibranch",
"questEggNudibranchMountText": "Nudibranch",
"questEggNudibranchText": "Nudibranchia",
"questEggNudibranchMountText": "Nudibranchia",
"questEggNudibranchAdjective": "a nifty",
"eggNotes": "Dapatkan obat penetas untuk diberikan pada telur ini, dan ia akan menetas menjadi <%= eggText(locale) %> yang <%= eggAdjective(locale) %>;",
"hatchingPotionBase": "Biasa",

View File

@@ -62,7 +62,7 @@
"surveysMultiple": "Membantu Habitica berkembang sebanyak <%= count %> kali, baik dengan mengisi survey atau membantu dalam pengujian utama. Terima kasih!",
"currentSurvey": "Kuisioner Sekarang",
"surveyWhen": "Lencana akan diberikan kepada semua peserta ketika survei telah diproses, pada akhir Maret.",
"blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (<%= hrefCommunityManagerEmail %>)",
"blurbInbox": "Di sinilah pesan pribadimu disimpan! Kamu dapat mengirimkan pesan ke seseorang dengan menekan ikon amplop di sebelah nama mereka di Kedai Minuman, Party, atau Guild Chat. Jika kamu menerima PM yang tidak pantas, kirimlah screenshot tersebut melalui email ke Lemoness (<%= hrefCommunityManagerEmail %>)",
"blurbGuildsPage": "Guild adalah grup obrolan bertopik yang dibuat oleh pengguna, untuk pengguna. Telusuri daftar tema dan bergabunglah dengan guild yang menarik untukmu!",
"blurbChallenges": "Tantangan dibuat oleh sesama pemain. Bergabung pada sebuah tantangan akan menambah tugas ke daftarmu, dan memenangkan tantangan akan memberikanmu lencana dan terkadang hadiah permata!",
"blurbHallPatrons": "Ini adalah Aula para Pelindung, di mana kami memberi penghormatan pada petualang pemberani yang membantu Kickstarter awal Habitica. Kami berterima kasih kepada mereka yang membantu kami menjadikan Habitica kenyataan!",

View File

@@ -134,7 +134,7 @@
"playButtonFull": "Masuk Habitica",
"presskit": "Paket Pres",
"presskitDownload": "Mengunduh seluruh gambar:",
"presskitText": "Thanks for your interest in Habitica! The following images can be used for articles or videos about Habitica. For more information, please contact Siena Leslie at <%= pressEnquiryEmail %>.",
"presskitText": "Terima kasih untuk ketertarikan anda pada Habitica! Gambar-gamar berikut ini dapat digunakan untuk artikel atau video mengenai Habitica. Untuk informasi lebih lanjut, silahkan hubungi Siena Leslie di <%= pressEnquiryEmail %>.",
"pkVideo": "Video",
"pkPromo": "Promo",
"pkLogo": "Logo",
@@ -259,7 +259,7 @@
"passwordResetEmailHtml": "Kalau kamu meminta reset kata sandi untuk <strong><%= username %></strong> di Habitica, <a href=\"<%= passwordResetLink %>\">klik di sini</a> untuk membuat kata sandi baru. Tautan akan hangus setelah 24 jam.<br/><br>Kalau kamu tidak pernah meminta reset kata sandi, abaikan saja email ini.",
"invalidLoginCredentialsLong": "O-oh - nama pengguna atau kata sandi tidak benar.\n- Pastikan nama pengguna atau email diketikkan dengan benar.\n- Kamu mungkin mendaftar dengan menggunakan Facebook, bukan email. Periksa ulang dengan mencoba masuk melalui Facebook.\n- Jika kamu lupa kata sandi, klik \"Lupa Kata Sandi\".",
"invalidCredentials": "Tidak ada akun yang menggunakan credential tersebut.",
"accountSuspended": "Account has been suspended, please contact <%= communityManagerEmail %> with your User ID \"<%= userId %>\" for assistance.",
"accountSuspended": "Akun anda telah disuspend, mohon hubungi <%= communityManagerEmail %> dengan User ID anda \"<%= userId %>\" untuk bantuan.",
"unsupportedNetwork": "Jaringan ini saat ini belum didukung.",
"cantDetachSocial": "Akun tidak memiliki metode autentikasi lain; tidak dapat memutuskan metode autentikasi ini.",
"onlySocialAttachLocal": "Autentikasi lokal dapat ditambahkan hanya pada akun sosial.",

View File

@@ -802,8 +802,8 @@
"headSpecialSpring2017MageNotes": "Questo cappello può aiutarti a lanciare potenti incantesimi... o lo puoi usare per evocare delle palline da tennis. A te la scelta. Aumenta la Percezione di <%= per %>. Edizione limitata, primavera 2017.",
"headSpecialSpring2017HealerText": "Tiara di Petali",
"headSpecialSpring2017HealerNotes": "Questa delicata corona emana il confortante aroma dei nuovi fiori primaverili. Aumenta lIntelligenza di <%= int %>. Edizione limitata, primavera 2017.",
"headSpecialSummer2017RogueText": "Sea Dragon Helm",
"headSpecialSummer2017RogueNotes": "This helm changes colors to help you blend in with your surroundings. Increases Perception by <%= per %>. Limited Edition 2017 Summer Gear.",
"headSpecialSummer2017RogueText": "Elmo del Drago Marino",
"headSpecialSummer2017RogueNotes": "Questo elmo cambia colore per aiutarti a mimettizarti nell'ambiente. Aumenta la Percezione di <%= per %>. Edizione limitata, estate 2017.",
"headSpecialSummer2017WarriorText": "Elmo Castello di Sabbia",
"headSpecialSummer2017WarriorNotes": "L'elmo più raffinato che si possa sperare di indossare... per lo meno fino all'arrivo della marea. Aumenta la Forza di <%= str % >. Edizione limitata, estate 2017.",
"headSpecialSummer2017MageText": "Whirlpool Hat",

View File

@@ -270,10 +270,10 @@
"backgroundOnATreeBranchText": "Op een Boomtak",
"backgroundOnATreeBranchNotes": "Strijk neer op een boomtak.",
"backgrounds062017": "SET 37: uitgebracht in juni 2017",
"backgroundBuriedTreasureText": "Begraven schat",
"backgroundBuriedTreasureNotes": "Graaf begraven schatten op.",
"backgroundOceanSunriseText": "Oceaan met zonsopgang",
"backgroundOceanSunriseNotes": "Bewonder een oceaan met zonsopgang.",
"backgroundBuriedTreasureText": "Begraven Schat",
"backgroundBuriedTreasureNotes": "Graaf verborgen schatten op.",
"backgroundOceanSunriseText": "Oceaan met Zonsopgang",
"backgroundOceanSunriseNotes": "Bewonder een zonsopgang boven de oceaan.",
"backgroundSandcastleText": "Zandkasteel",
"backgroundSandcastleNotes": "Heers over een zandkasteel",
"backgrounds072017": "SET 38: Uitgebracht in juli 2017",

View File

@@ -112,6 +112,6 @@
"dateEndApril": "19 april",
"dateEndMay": "17 mei",
"dateEndJune": "14 juni",
"dateEndJuly": "Juli 29",
"dateEndJuly": "29 juli",
"discountBundle": "bundel"
}

View File

@@ -74,7 +74,7 @@
"earnedCompanion": "Door al jouw productiviteit heb je een nieuwe kameraad verdiend. Voer hem en laat hem groeien! ",
"feedPet": "<%= text %> aan je <%= name %> voeren?",
"useSaddle": "<%= pet %> zadelen?",
"raisedPet": "undefined",
"raisedPet": "Je hebt je <%= pet %> laten opgroeien!",
"earnedSteed": "Door het voltooien van taken heb je een trouw ros verdiend!",
"rideNow": "Berijd nu",
"rideLater": "Berijd later",

View File

@@ -1081,7 +1081,7 @@
"shieldSpecialSummer2017WarriorText": "Scallop Shield",
"shieldSpecialSummer2017WarriorNotes": "This shell that you just found is both decorative AND defensive! Increases Constitution by <%= con %>. Limited Edition 2017 Summer Gear.",
"shieldSpecialSummer2017HealerText": "Щит из раковины моллюска",
"shieldSpecialSummer2017HealerNotes": "Этот щит волшебной устрицы постоянно порождает жемчуг так де как и защищает. Увеличивает телосложение на <%= con %>. Ограниченный выпуск лета 2017.",
"shieldSpecialSummer2017HealerNotes": "Этот щит волшебной устрицы постоянно порождает жемчуг, а так же защищает. Увеличивает телосложение на <%= con %>. Ограниченный выпуск лета 2017.",
"shieldMystery201601Text": "Уничтожитель Решительности",
"shieldMystery201601Notes": "Этот клинок может быть использован, чтобы парировать все отвлечения. Бонусов не дает. Подарок подписчикам января 2016.",
"shieldMystery201701Text": "Время-Замораживающий Щит",

View File

@@ -209,7 +209,7 @@
"onlyCreatorOrAdminCanDeleteChat": "Вы не авторизованы чтобы удаить это сообщение!",
"onlyGroupLeaderCanEditTasks": "Вы не авторизованы, чтобы редактировать задачи!",
"onlyGroupTasksCanBeAssigned": "Можно назначать только командные задачи",
"chatPrivilegesRevoked": "Your chat privileges have been revoked.",
"chatPrivilegesRevoked": "Ваши права в чате были отменены.",
"newChatMessagePlainNotification": "Новое сообщение в <%= groupName %> от <%= authorName %>. Нажмите, чтобы открыть чат!",
"newChatMessageTitle": "Новое сообщение в <%= groupName %>",
"exportInbox": "Экспортировать сообщения",

View File

@@ -169,5 +169,5 @@
"missingCustomerId": "Отсутствует req.query.customerId",
"missingPaypalBlock": "Отсутствует req.session.paypalBlock",
"missingSubKey": "Отсутствует req.query.sub",
"paypalCanceled": "Your subscription has been canceled"
"paypalCanceled": "Ваша подписка была отменена"
}

Some files were not shown because too many files have changed in this diff Show More