Skip to content

[IMP] tutorials: first commit #353

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 27 commits into
base: 18.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2b35be3
[IMP] tutorials: first commit
roto-odoo Feb 17, 2025
5422632
[ADD] estate: creation of Real Estate App (Chapter 2)
roto-odoo Feb 17, 2025
4a606ca
[IMP] estate: model creation (Chapter 3)
roto-odoo Feb 17, 2025
201e16d
[IMP] estate: security rules (Chapter 4)
roto-odoo Feb 17, 2025
958d6d5
[IMP] estate: first user interfaces (Chapter 5)
roto-odoo Feb 17, 2025
a20009a
[IMP] estate: custom user interfaces (Chapter 6) + first review fixes
roto-odoo Feb 18, 2025
f61efe4
[IMP] estate: relation between models (Chapter 7)
roto-odoo Feb 18, 2025
3931392
[IMP] estate: computed fields and onchanges (Chapter 8)
roto-odoo Feb 19, 2025
400e59e
[IMP] estate: Action Buttons (Chapter 9)
roto-odoo Feb 19, 2025
d7146b6
[IMP] estate: SQL and Python constraints (Chapter 10)
roto-odoo Feb 19, 2025
4737208
[IMP] estate: UI configs (Chapter 11 - part 1)
roto-odoo Feb 19, 2025
0761017
[IMP] estate: UI configs (Chapter 11 - part 2)
roto-odoo Feb 19, 2025
ad6f943
[IMP] estate: UI configs (Chapter 11 - part 3)
roto-odoo Feb 20, 2025
52f7262
[IMP] estate: Inheritance (Chapter 12)
roto-odoo Feb 20, 2025
f7069d9
[IMP] estate: Link Module (Chapter 13 - part 1)
roto-odoo Feb 20, 2025
c3bba62
[IMP] estate: Link Module (Chapter 13 - part 2)
roto-odoo Feb 20, 2025
1a23c78
[IMP] estate: QWeb (Chapter 14 - part 1)
roto-odoo Feb 20, 2025
78a306f
[IMP] estate: QWeb (Chapter 14 - part 2)
roto-odoo Feb 21, 2025
f7236c1
[IMP] awesome_owl: 1 - 10
roto-odoo Feb 21, 2025
491e8c0
[IMP] awesome_owl: 10-14
roto-odoo Feb 24, 2025
90248c9
[IMP] awesome_dashboard : Chapter 1 to 5
roto-odoo Feb 24, 2025
f642678
[IMP] awesome_dashboard : Chapter 5 to 7
roto-odoo Feb 25, 2025
93121a3
[IMP] awesome_dashboard : Chapter 7 to end
roto-odoo Feb 26, 2025
bdcb1c6
[IMP] awesome_clicker: Chapter 1 to 5
roto-odoo Feb 26, 2025
0751346
[IMP] awesome_clicker: Chapter 5 to 16
roto-odoo Feb 28, 2025
7c22bab
[IMP] awesome_clicker: Chapter 16 to 19
roto-odoo Feb 28, 2025
40352eb
[IMP] restrict access data
roto-odoo Mar 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions awesome_clicker/static/src/click_reward.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { choose } from "./utils";


const rewards = [
{
description: "Get 1 click bot",
apply(clicker) {
clicker.increment(1);
},
maxLevel: 3,
},
{
description: "Get 10 click bots",
apply(clicker) {
clicker.increment(10);
},
minLevel: 3,
maxLevel: 4,
},
{
description: "Increase bot power",
apply(clicker) {
clicker.power += 1;
},
minLevel: 3,
},
];

export function getReward(level){
const availableRewards = rewards.filter((reward) => {
return (level >= (reward?.minLevel || level)) && (level <= (reward?.maxLevel || level));
});
return choose(availableRewards);
}
16 changes: 16 additions & 0 deletions awesome_clicker/static/src/click_value/click_value.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Component } from "@odoo/owl";
import { humanNumber } from "@web/core/utils/numbers";
import { useClicker } from "../clicker_hook";


export class ClickValue extends Component {
static template = "awesome_clicker.click_value";

setup() {
this.clicker = useClicker();
}

get clicks(){
return humanNumber(this.clicker.clicks, { decimals: 1 });
}
}
10 changes: 10 additions & 0 deletions awesome_clicker/static/src/click_value/click_value.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">

<t t-name="awesome_clicker.click_value">
<span t-att-data-tooltip="clicker.clicks">
<t t-esc="clicks"/>
</span>
</t>

</templates>
7 changes: 7 additions & 0 deletions awesome_clicker/static/src/clicker_hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useService } from "@web/core/utils/hooks";
import { useState } from "@odoo/owl";


export function useClicker() {
return useState(useService("clickerService"));
}
128 changes: 128 additions & 0 deletions awesome_clicker/static/src/clicker_model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/* @odoo-module */
import { Reactive } from "@web/core/utils/reactive";
import { EventBus } from "@odoo/owl";
import { getReward } from "./click_reward";


export class ClickerModel extends Reactive {
constructor(state){
super();
this._clicks = state?._clicks || 0;
this._level = state?._level || 0;
this._power = state?._power || 1;
this._bots = {
clickBot: {
price: 1000,
level: 1,
increment: 10,
purchased: state?._bots?.clickBot?.purchased || 0,
},
bigBot: {
price: 5000,
level: 2,
increment: 100,
purchased: state?._bots?.bigBot?.purchased || 0,
},
};
this.trees = {
pearTree: {
price: 1000000,
level: 4,
produce: "pear",
purchased: state?.trees?.pearTree?.purchased || 0
},
cherryTree: {
price: 1000000,
level: 4,
produce: "cherry",
purchased: state?.trees?.cherryTree?.purchased || 0
},
};
this.fruits = {
pear: state?.fruits?.pear || 0,
cherry: state?.fruits?.cherry || 0,
};
this._milestones = [
{ clicks: 1000, unlock: "clickBot" },
{ clicks: 5000, unlock: "bigBot" },
{ clicks: 100000, unlock: "power multiplier" },
{ clicks: 1000000, unlock: "trees" },
];
this._bus = new EventBus();
this._tick = state?._tick || 0;
}

setup(){}

get clicks(){ return this._clicks; }

get level(){ return this._level; }

get power() { return this._power; }
set power(pwr){ this._power = pwr; }

get bots() { return this._bots; }

get firePower(){
let sum = 0;
for(const bot in this._bots){
sum += this._bots[bot].purchased * this._bots[bot].increment * this._power;
}
return sum;
}

get bus(){ return this._bus; }

increment(inc){
this._clicks += inc;
if(this._milestones[this._level]
&& this._clicks >= this._milestones[this._level].clicks){
this.bus.trigger("MILESTONE", this._milestones[this._level]);
this._level++;
}
}

tick(){
this._tick++;
for(const bot in this._bots){
this.increment(this._bots[bot].increment * this._power * this._bots[bot].purchased);
}
if(this._tick === 3){
for(const tree in this.trees){
this.fruits[this.trees[tree].produce] += this.trees[tree].purchased;
}
this._tick = 0;
}
}

buyBot(name){
if(!Object.keys(this._bots).includes(name)){
throw new Error(`Invalid bot name ${name}`);
}
if(this._clicks < this._bots[name].price){
return false;
}
this.increment(-this._bots[name].price);
this._bots[name].purchased++;
}

buyPower(nb){
if(this._clicks >= 50000 && this._level >= 3){
this._power += nb;
this.increment(-50000*nb);
}
}

buyTree(name){
if(this._clicks >= this.trees[name].price){
this.trees[name].purchased++;
this.increment(-this.trees[name].price);
}
}

getReward(){
const reward = getReward(this._level);
this._bus.trigger("REWARD", reward);
}

}
31 changes: 31 additions & 0 deletions awesome_clicker/static/src/clicker_provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* @odoo-module */
import { registry } from "@web/core/registry";


const clickerProviderRegistry = registry.category("command_provider");

clickerProviderRegistry.add("clicker", {
provide: (env, options) => {
return [
{
action(){
env.services["clickerService"].buyBot(1);
},
category: "clicker",
name: "Buy 1 click bot"
},
{
action(){
env.services.action.doAction({
type: "ir.actions.client",
tag: "awesome_clicker.client_action",
target: "new",
name: "Clicker Game"
});
},
category: "clicker",
name: "Open Clicker Game"
},
]
}
});
63 changes: 63 additions & 0 deletions awesome_clicker/static/src/clicker_service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { registry } from "@web/core/registry";
import { ClickerModel } from "./clicker_model";
import { EventBus } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { browser } from "@web/core/browser/browser";


const clickerService = {
dependencies: ["action", "effect", "notification"],
start(env, services){
const clicker_state_storage = browser.localStorage.getItem("clicker_state");
let state = undefined;
if(clicker_state_storage){
state = JSON.parse(clicker_state_storage);
}
const clicker_model = new ClickerModel(state);

const bus = clicker_model.bus;
bus.addEventListener("MILESTONE", (ev) => {
services.effect.add({
type: "rainbow_man",
message: `Milestone reached! You can now buy ${ev.detail.unlock}`,
});
});

bus.addEventListener("REWARD", (ev) => {
const reward = ev.detail;
const closeNotification = services.notification.add(
`Congrats you won a reward: "${reward.description}"`,
{
type: "success",
sticky: true,
buttons: [
{
name: "Collect",
onClick: () => {
reward.apply(clicker_model);
closeNotification();
services.action.doAction({
type: "ir.actions.client",
tag: "awesome_clicker.client_action",
target: "new",
name: "Clicker Game"
});
}
}
]
}
);
});

document.addEventListener("click", () => clicker_model.increment(1), true);
setInterval(() => {
clicker_model.tick();
browser.localStorage.setItem("clicker_state", JSON.stringify(clicker_model));
}, 10000);


return clicker_model;
}
};

registry.category("services").add("clickerService", clickerService);
58 changes: 58 additions & 0 deletions awesome_clicker/static/src/clicker_systray_item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { registry } from "@web/core/registry";
import { Component, useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import { useClicker } from "./clicker_hook";
import { ClickValue } from "./click_value/click_value";
import { Dropdown } from "@web/core/dropdown/dropdown";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";


class Clicker extends Component {
static template = "awesome_clicker.clicker_systray_item";
static components = { ClickValue, Dropdown, DropdownItem };

setup(){
this.clicker = useClicker();
this.action = useService("action");
}

increment(){
this.clicker.increment(9);
}

openClientAction(){
this.action.doAction({
type: "ir.actions.client",
tag: "awesome_clicker.client_action",
target: "new",
name: "Clicker Game"
});
}

get numberTrees() {
let sum = 0;
for(const tree in this.clicker.trees){
sum += this.clicker.trees[tree].purchased;
}
return sum;
}

get numberFruits() {
let sum = 0;
for(const fruit in this.clicker.fruits){
sum += this.clicker.fruits[fruit];
}
return sum;
}

get numberBots(){
let sum = 0;
for(const bot in this.clicker.bots){
sum += this.clicker.bots[bot].purchased;
}
return sum;
}

}

registry.category("systray").add("awesome_clicker.Clicker", { Component: Clicker });
40 changes: 40 additions & 0 deletions awesome_clicker/static/src/clicker_systray_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="awesome_clicker.clicker_systray_item">
<div class="o_nav_entry">
<Dropdown>
<button>
<ClickValue/> <i class="fa fa-mouse-pointer fa-fw"/>
<t t-esc="this.numberBots"/> <i class="fa fa-android fa-fw"/>
<t t-esc="this.numberTrees"/> <i class="fa fa-tree fa-fw"/>
<t t-esc="this.numberFruits"/> <i class="fa fa-apple fa-fw"/>
</button>
<t t-set-slot="content">
<DropdownItem>
<button class="btn btn-secondary" t-on-click="this.openClientAction">
Open the clicker game
</button>
</DropdownItem>
<DropdownItem>
<button class="btn btn-secondary" t-on-click="() => this.clicker.buyBot('clickBot')">
Buy a ClickBot
</button>
</DropdownItem>
<DropdownItem t-foreach="this.clicker.bots" t-as="bot" t-key="bot">
<t t-esc="bot_value.purchased"/>x
<t t-esc="bot"/>
</DropdownItem>
<DropdownItem t-foreach="this.clicker.trees" t-as="tree" t-key="tree">
<t t-esc="tree_value.purchased"/>x
<t t-esc="tree"/>
</DropdownItem>
<DropdownItem t-foreach="this.clicker.fruits" t-as="fruit" t-key="fruit">
<t t-esc="fruit_value"/>x
<t t-esc="fruit"/>
</DropdownItem>
</t>

</Dropdown>
</div>
</t>
</templates>
Loading