Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e99d693
[IMP] awesome_owl: Technical onboarding, chapter 1, step 1 to 5
fmalfroid Sep 22, 2025
a5aa410
[IMP] awesome_owl: Discover the web framework step 6: The sum of two …
fmalfroid Sep 22, 2025
cdc5670
[IMP] awesome_owl: Discover the web framework step 7: A todo list
fmalfroid Sep 22, 2025
045c2fd
[IMP] awesome_owl: Discover the web framework step 8: Use dynamic att…
fmalfroid Sep 22, 2025
103de17
[IMP] awesome_owl: Discover the web framework step 9: Adding a todo
fmalfroid Sep 22, 2025
9b751cc
[IMP] awesome_owl: Discover the web framework step 10: Focusing the i…
fmalfroid Sep 22, 2025
a1b31a0
[IMP] awesome_owl: Discover the web framework step 11: Toggling todos
fmalfroid Sep 23, 2025
295aaf0
[IMP] awesome_owl: Discover the web framework step 12: Deleting todos
fmalfroid Sep 23, 2025
6866ff9
[IMP] awesome_owl: Discover teh web framework step 13: Generic Card w…
fmalfroid Sep 23, 2025
1bf3b4a
[IMP] awesome_owl: Discover the web framework Chapter 1: Owl components
fmalfroid Sep 23, 2025
d8c3e56
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 23, 2025
0cc3c1d
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, step …
fmalfroid Sep 23, 2025
17662bf
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 23, 2025
d2432e2
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 23, 2025
fb73735
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 23, 2025
15347e5
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 23, 2025
cd512a5
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 23, 2025
096972b
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 24, 2025
46a5eca
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, step …
fmalfroid Sep 24, 2025
15df7b2
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, step …
fmalfroid Sep 24, 2025
58fffbe
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 24, 2025
346aea5
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, Step …
fmalfroid Sep 24, 2025
a3d43b7
[IMP] awesome_dashboard: Discover the web framework, Chapter 2, list …
fmalfroid Sep 24, 2025
7cb706e
[IMP] awesome_owl: modifications based on review comments
fmalfroid Sep 25, 2025
bea33c7
[IMP] awesome_owl: changed useAutoFocus to use onMounted instead of u…
fmalfroid Sep 25, 2025
8344dc3
[IMP] awesome_dashboard: Cards are 100% of the width on mobile
fmalfroid Sep 25, 2025
d1d1bac
[IMP] awesome_dashboard: scrollable dashboard
fmalfroid Sep 25, 2025
6ad8344
[IMP] awesome_clicker: Built a clicker game for Matser the web framew…
fmalfroid Sep 30, 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
15 changes: 15 additions & 0 deletions awesome_clicker/static/src/ClickValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Component } from "@odoo/owl";
import { humanNumber } from "@web/core/utils/numbers"

export class ClickValue extends Component {
static template = "awesome_clicker.ClickValue";
static props = {
clicks: Number
}

setup() {
this.humanize = (clicks) => {
return humanNumber(clicks, {decimals: clicks > 1000 ? 1 : 0});
}
}
}
13 changes: 13 additions & 0 deletions awesome_clicker/static/src/ClickValue.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>

<templates xml:space="preserve">

<t t-name="awesome_clicker.ClickValue">
<div class="d-flex flex-row align-items-center">
<span t-att-data-tooltip="props.clicks">
<t t-esc="humanize(props.clicks)"/>
</span>
</div>
</t>

</templates>
24 changes: 24 additions & 0 deletions awesome_clicker/static/src/click_rewards.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export const rewards = [
{
description: "Get 1 click bot",
apply(clicker) {
clicker.bots[0].amount += 1;
},
maxLevel: 3,
},
{
description: "Get 10 click bot",
apply(clicker) {
clicker.bots[0].amount += 10;
},
minLevel: 3,
maxLevel: 4,
},
{
description: "Increase bot power!",
apply(clicker) {
clicker.power += 1;
},
minLevel: 3,
},
];
23 changes: 23 additions & 0 deletions awesome_clicker/static/src/clicker_migration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const CURRENT_VERSION = 2.0;
export const migrations = [
{
fromVersion: 1.0,
toVersion: 2.0,
apply: (state) => {
state.trees.push({fruit: "Peach", price: 1000000, amount: 0, fruit_number: 0 })
},
},
];

export function migrate(localState) {
if (localState?.version < CURRENT_VERSION) {
for (const migration of migrations) {
if (localState.version === migration.fromVersion) {
migration.apply(localState);
localState.version = migration.toVersion
}
}
localState.version = CURRENT_VERSION;
}
return localState;
}
94 changes: 94 additions & 0 deletions awesome_clicker/static/src/clicker_model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Reactive } from "@web/core/utils/reactive";
import { EventBus } from "@odoo/owl";
import { rewards } from "./click_rewards";
import { choose } from "./utils";
import { CURRENT_VERSION } from "./clicker_migration";

export class ClickerModel extends Reactive {
constructor() {
super();
this.version = CURRENT_VERSION;
this.clicks = 0;
this.level = 0;
this.power = 1;
this.bus = new EventBus();
this.milestones = [
{ clicks: 1000, unlock: "ClickBots" },
{ clicks: 5000, unlock: "BigBots"},
{ clicks: 100000, unlock: "Power multipliers"},
{ clicks: 1000000, unlock: "Trees" }
]
this.bots = [
{ name: "Click Bot", price: 1000, amount: 0, clicks: 10 },
{ name: "Big Bot", price: 5000, amount: 0, clicks: 100 },
]
this.trees = [
{ fruit: "Pear", price: 1000000, amount: 0, fruit_number: 0 },
{ fruit: "Cherry", price: 1000000, amount: 0, fruit_number: 0 },
{ fruit: "Apple", price: 1000000, amount: 0, fruit_number: 0 },
{ fruit: "Orange", price: 1000000, amount: 0, fruit_number: 0 },
{ fruit: "Peach", price: 1000000, amount: 0, fruit_number: 0 },
]
}

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 += 1;
this.getReward()
}
}

buyBot(bot) {
if (this.clicks >= bot.price) {
bot.amount++;
this.clicks -= bot.price;
}
}

buyClickBot() {
this.buyBot(this.bots[0]);
}

buyBigBot() {
this.buyBot(this.bots[1]);
}

buyPower() {
if (this.clicks >= 50000) {
this.power++;
this.clicks -= 50000;
}
}

buyTree(tree) {
if (this.clicks >= tree.price) {
tree.amount++;
this.clicks -= tree.price;
}
}

getReward() {
const reward = choose(this, rewards);
this.bus.trigger("REWARD", reward);
}

getTotalTrees() {
let total = 0;
this.trees.forEach((tree) => total += tree.amount)
return total;
}

getTotalFruits() {
let total = 0;
this.trees.forEach((tree) => total += tree.fruit_number)
return total;
}

toJSON() {
const json = Object.assign({}, this);
delete json["bus"];
return json;
}
}
27 changes: 27 additions & 0 deletions awesome_clicker/static/src/clicker_provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { registry } from "@web/core/registry";

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

commandProviderRegistry.add("clicker", {
provide: (env, options) => {
return [
{
name: "Buy 1 click bot",
action() {
const clicker = env.services["awesome_clicker.clicker_service"].buyClickBot();
},
},
{
name: "Open Clicker Game",
action() {
env.services.action.doAction({
type: "ir.actions.client",
tag: "awesome_clicker.client_action",
target: "new",
name: "Clicker Game",
});
},
},
];
},
});
53 changes: 53 additions & 0 deletions awesome_clicker/static/src/clicker_service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { registry } from "@web/core/registry";
import { ClickerModel } from "./clicker_model";
import { migrate } from "./clicker_migration";

const clickerService = {
dependencies: ["effect", "notification"],
start(env, services) {
const localClicker = migrate(JSON.parse(localStorage.getItem("clicker")))
const clicker = localClicker ? Object.assign(new ClickerModel(), localClicker) : new ClickerModel()

setInterval(() => {
clicker.bots.forEach((bot) => clicker.increment(clicker.power * bot.amount * bot.clicks));
}, 10*1000);

setInterval(() => {
clicker.trees.forEach((tree) => tree.fruit_number += tree.amount)
}, 30*1000)

setInterval(() => {
localStorage.setItem("clicker", JSON.stringify(clicker));
}, 10*1000);

const bus = clicker.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);
closeNotification();
},
},
],
}
);
})

return clicker
},
};

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

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

setup() {
this.clicker = useClicker();
this.action = useService("action");
useExternalListener(document.body, "click", () => this.clicker.increment(1), { capture: true });
}

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

export const systrayItem = {
Component: ClickerSystray,
};

registry.category("systray").add("awesome_clicker.ClickerSystray", systrayItem, { sequence: 10 });
34 changes: 34 additions & 0 deletions awesome_clicker/static/src/clicker_systray_item.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" ?>

<templates xml:space="preserve">

<t t-name="awesome_clicker.ClickerSystray">
<Dropdown>
<button>
<ClickValue clicks="this.clicker.clicks"/> <i class="fa fa-mouse-pointer fa-fw"></i>,
<t t-esc="this.clicker.getTotalTrees()"/> <i class="fa fa-tree fa-fw"></i>,
<t t-esc="this.clicker.getTotalFruits()"/> <i class="fa fa-apple fa-fw"></i>
</button>

<t t-set-slot="content">
<DropdownItem>
<button class="btn btn-secondary" t-on-click="openClicker">Open the clicker game</button>
</DropdownItem>
<DropdownItem>
<button class="btn btn-secondary" t-on-click="this.clicker.buyClickBot">Buy a Click Bot</button>
</DropdownItem>
<t t-foreach="this.clicker.trees" t-as="tree" t-key="tree.fruit">
<DropdownItem>
<t t-esc="tree.amount + 'x ' + tree.fruit + ' Tree'"/>
</DropdownItem>
</t>
<t t-foreach="this.clicker.trees" t-as="tree" t-key="tree.fruit">
<DropdownItem>
<t t-esc="tree.fruit_number + 'x ' + tree.fruit"/>
</DropdownItem>
</t>
</t>
</Dropdown>
</t>

</templates>
16 changes: 16 additions & 0 deletions awesome_clicker/static/src/client_action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Component } from "@odoo/owl"
import { registry } from "@web/core/registry";
import { useClicker } from "./hooks";
import { ClickValue } from './ClickValue';
import { Notebook } from "@web/core/notebook/notebook";

export class ClientAction extends Component {
static template = "awesome_clicker.ClientAction";
static components = { ClickValue, Notebook }

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

registry.category("actions").add("awesome_clicker.client_action", ClientAction);
Loading