diff --git a/awesome_clicker/__manifest__.py b/awesome_clicker/__manifest__.py index 56dc2f779b9..e57ef4d5bb0 100644 --- a/awesome_clicker/__manifest__.py +++ b/awesome_clicker/__manifest__.py @@ -12,7 +12,7 @@ 'author': "Odoo", 'website': "https://www.odoo.com/", - 'category': 'Tutorials', + 'category': 'Tutorials/AwesomeClicker', 'version': '0.1', 'application': True, 'installable': True, diff --git a/awesome_clicker/static/src/clicker_hook.js b/awesome_clicker/static/src/clicker_hook.js new file mode 100644 index 00000000000..2b84c85a327 --- /dev/null +++ b/awesome_clicker/static/src/clicker_hook.js @@ -0,0 +1,7 @@ +import { useState } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; + + +export function useClicker() { + return useState(useService("awesome_clicker.clicker_service")) +} diff --git a/awesome_clicker/static/src/clicker_migration.js b/awesome_clicker/static/src/clicker_migration.js new file mode 100644 index 00000000000..62c75b158f8 --- /dev/null +++ b/awesome_clicker/static/src/clicker_migration.js @@ -0,0 +1,29 @@ +export const CURRENT_VERSION = 2.0; +export const migrations = [ + { + fromVersion: 1.0, + toVersion: 2.0, + apply: (state) => { + state.trees.peachTree = { + price: 1500000, + level: 4, + fruit: "peach", + purchased: 0, + }; + state.fruits.peach = 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; +} diff --git a/awesome_clicker/static/src/clicker_model.js b/awesome_clicker/static/src/clicker_model.js new file mode 100644 index 00000000000..6a7c8ca846a --- /dev/null +++ b/awesome_clicker/static/src/clicker_model.js @@ -0,0 +1,145 @@ +import { Reactive } from "@web/core/utils/reactive"; +import { EventBus } from "@odoo/owl"; +import { rewards } from "./clicker_reward"; +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.bots = { + clickbot: { + price: 1000, + increment: 10, + purchased: 0, + level: 1, + }, + bigbot: { + price: 5000, + increment: 100, + purchased: 0, + level: 2, + } + }; + this.trees = { + pearTree: { + price: 1000000, + level: 4, + purchased: 0, + fruit: "pear", + }, + cherryTree: { + price: 1000000, + level: 4, + purchased: 0, + fruit: "cherry", + }, + appleTree: { + price: 1000000, + level: 4, + purchased: 0, + fruit: "apple", + }, + peachTree: { + price: 1500000, + level: 4, + fruit: "peach", + purchased: 0, + } + }; + this.fruits = { + pear: 0, + cherry: 0, + apple: 0, + peach: 0, + + } + this.bus = new EventBus(); + } + + ticks() { + for (const bot in this.bots) { + this.clicks += this.bots[bot].purchased * this.bots[bot].increment * this.power; + } + } + + updateFruits() { + for (const tree in this.trees) { + this.fruits[this.trees[tree].fruit] += this.trees[tree].purchased; + } + } + + increment(incr) { + this.clicks += incr; + if (this.level < 1 && this.clicks >= 1000) { + this.bus.trigger("MILESTONE_1k"); + this.level++; + } + if (this.level < 2 && this.clicks >= 5000) { + this.bus.trigger("MILESTONE_5k"); + this.level++; + } + if (this.level < 3 && this.clicks >= 100000) { + this.bus.trigger("MILESTONE_100k"); + this.level++; + } + if (this.level < 4 && this.clicks >= 1000000) { + this.bus.trigger("MILESTONE_1M"); + this.level++; + } + }; + + buyBot(bot) { + if (this.clicks < this.bots[bot].price){ + return false; + } + this.clicks -= this.bots[bot].price; + this.bots[bot].purchased++; + }; + + buyPower() { + if (this.clicks < 50000){ + return false; + } + this.power++; + this.clicks -= 50000; + } + + buyTree(tree) { + if (this.clicks < 1000000){ + return false; + } + this.trees[tree].purchased++; + this.clicks -= 1000000; + } + + giveReward() { + const availableRewards = []; + for (const reward of rewards) { + if (reward.minLevel <= this.level || !reward.minLevel){ + if (reward.maxLevel >= this.level || !reward.maxLevel){ + availableRewards.push(reward); + } + } + } + const reward = choose(availableRewards); + this.bus.trigger("REWARD", reward); + } + + toJSON() { + const json = Object.assign({}, this); + delete json["bus"]; + return json; + } + + static fromJSON(json) { + const clicker = new ClickerModel(); + const clickerInstance = Object.assign(clicker, json); + return clickerInstance; + } +} diff --git a/awesome_clicker/static/src/clicker_provider.js b/awesome_clicker/static/src/clicker_provider.js new file mode 100644 index 00000000000..3bd24ab38d6 --- /dev/null +++ b/awesome_clicker/static/src/clicker_provider.js @@ -0,0 +1,35 @@ +import { registry } from "@web/core/registry"; + +const commandProviderRegistry = registry.category("command_provider"); + +commandProviderRegistry.add("clicker", { + provide: (env, options) => { + return [ + { + name: "Open Clicker Game", + action() { + env.services.action.doAction({ + type: "ir.actions.client", + tag: "awesome_clicker.client_action", + target: "new", + name: "Clicker Game", + }); + + }, + }, + { + name: "Buy one ClickBot", + action() { + env.services["awesome_clicker.clicker_service"].buyBot("clickbot"); + env.services.action.doAction({ + type: "ir.actions.client", + tag: "awesome_clicker.client_action", + target: "new", + name: "Clicker Game", + }); + } + } + ]; + }, +}); + \ No newline at end of file diff --git a/awesome_clicker/static/src/clicker_reward.js b/awesome_clicker/static/src/clicker_reward.js new file mode 100644 index 00000000000..d5c2f0fcc28 --- /dev/null +++ b/awesome_clicker/static/src/clicker_reward.js @@ -0,0 +1,24 @@ +export const rewards = [ + { + description: "Get 1 click bot", + apply(clicker) { + clicker.bots.clickbot.purchased++; + }, + maxLevel: 3, + }, + { + description: "Get 1 big bot", + apply(clicker) { + clicker.bots.bigbot.purchased++; + }, + minLevel: 3, + maxLevel: 4, + }, + { + description: "Increase bot power!", + apply(clicker) { + clicker.power += 1; + }, + minLevel: 3, + }, +]; diff --git a/awesome_clicker/static/src/clicker_service.js b/awesome_clicker/static/src/clicker_service.js new file mode 100644 index 00000000000..bb56f09f2b0 --- /dev/null +++ b/awesome_clicker/static/src/clicker_service.js @@ -0,0 +1,75 @@ +import { registry } from "@web/core/registry"; +import { ClickerModel } from "./clicker_model"; +import { browser } from "@web/core/browser/browser"; +import { migrate } from "./clicker_migration"; + + +const clickerService = { + dependencies: ["action", "effect", "notification"], + start(env, services) { + const localState = migrate(JSON.parse(browser.localStorage.getItem("clickerState"))); + const model = localState ? ClickerModel.fromJSON(localState): new ClickerModel(); + + setInterval(() => model.ticks(), 10000); + setInterval(() => model.updateFruits(), 30000); + + setInterval(() => { + browser.localStorage.setItem("clickerState", JSON.stringify(model)) + }, 10000); + + const bus = model.bus; + bus.addEventListener("MILESTONE_1k", () => { + services.effect.add({ + type: "rainbow_man", + message: "Milestone reached! You can now buy ClickBots", + }); + }); + bus.addEventListener("MILESTONE_5k", () => { + services.effect.add({ + type: "rainbow_man", + message: "Milestone reached! You can now buy BigBots", + }); + }); + bus.addEventListener("MILESTONE_100k", () => { + services.effect.add({ + type: "rainbow_man", + message: "Milestone reached! You can now buy Powers", + }); + }); + bus.addEventListener("MILESTONE_1M", () => { + services.effect.add({ + type: "rainbow_man", + message: "Milestone reached! You can now buy Trees", + }); + }); + 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(model); + closeNotification(); + services.action.doAction({ + type: "ir.actions.client", + tag: "awesome_clicker.client_action", + target: "new", + name: "Clicker Game" + }); + }, + }, + ], + } + ); + }) + + return model; + } +} + +registry.category("services").add("awesome_clicker.clicker_service", clickerService); diff --git a/awesome_clicker/static/src/clicker_systray_item/clicker_systray_item.js b/awesome_clicker/static/src/clicker_systray_item/clicker_systray_item.js new file mode 100644 index 00000000000..c1ac2fbcb8f --- /dev/null +++ b/awesome_clicker/static/src/clicker_systray_item/clicker_systray_item.js @@ -0,0 +1,54 @@ +import { Component, useExternalListener } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { useClicker } from "../clicker_hook"; +import { ClickerValue } from "../clicker_value/clicker_value"; +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 = { ClickerValue, Dropdown, DropdownItem }; + + setup() { + this.client_action = useService("action"); + this.clicker = useClicker(); + useExternalListener(document.body, "click", this.incrementCounter, true); + } + + incrementCounter(){ + this.clicker.increment(1); + } + + openClientAction() { + this.client_action.doAction({ + type: "ir.actions.client", + tag: "awesome_clicker.client_action", + target: "new", + name: "Clicker Game" + }); + } + + get numberTrees() { + let numberOfTrees = 0; + for (const tree in this.clicker.trees) { + numberOfTrees += this.clicker.trees[tree].purchased; + } + return numberOfTrees; + } + + get numberFruits() { + let numberOfFruits = 0; + for (const fruit in this.clicker.fruits) { + numberOfFruits += this.clicker.fruits[fruit]; + } + return numberOfFruits; + } + +} + +export const systrayItem = { + Component: ClickerSystray, +}; + +registry.category("systray").add("awesome_clicker.ClickerSystray", systrayItem, { sequence: 1000 }); diff --git a/awesome_clicker/static/src/clicker_systray_item/clicker_systray_item.xml b/awesome_clicker/static/src/clicker_systray_item/clicker_systray_item.xml new file mode 100644 index 00000000000..9675468bebc --- /dev/null +++ b/awesome_clicker/static/src/clicker_systray_item/clicker_systray_item.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + x + + + x + + + + + diff --git a/awesome_clicker/static/src/clicker_value/clicker_value.js b/awesome_clicker/static/src/clicker_value/clicker_value.js new file mode 100644 index 00000000000..8658253a066 --- /dev/null +++ b/awesome_clicker/static/src/clicker_value/clicker_value.js @@ -0,0 +1,17 @@ +import { Component } from "@odoo/owl"; +import { humanNumber } from "@web/core/utils/numbers"; +import { useClicker } from "../clicker_hook"; + +export class ClickerValue extends Component { + static template = "awesome_clicker.ClickerValue"; + + setup() { + this.clicker = useClicker(); + } + + get humanizedClicks() { + return humanNumber(this.clicker.clicks, { + decimals: 1, + }); + } +} diff --git a/awesome_clicker/static/src/clicker_value/clicker_value.xml b/awesome_clicker/static/src/clicker_value/clicker_value.xml new file mode 100644 index 00000000000..24fd29643f2 --- /dev/null +++ b/awesome_clicker/static/src/clicker_value/clicker_value.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/awesome_clicker/static/src/client_action/client_action.js b/awesome_clicker/static/src/client_action/client_action.js new file mode 100644 index 00000000000..0d4cfc692dc --- /dev/null +++ b/awesome_clicker/static/src/client_action/client_action.js @@ -0,0 +1,18 @@ +import { registry } from "@web/core/registry"; +import { Component, useState } from "@odoo/owl"; +import { useClicker } from "../clicker_hook"; +import { ClickerValue } from "../clicker_value/clicker_value"; +import { Notebook } from "@web/core/notebook/notebook"; + +export class ClientAction extends Component { + static template = "awesome_clicker.ClientAction"; + static props = ["*"]; + static components = { ClickerValue, Notebook }; + + setup() { + this.clicker = useClicker(); + } + +} + +registry.category("actions").add("awesome_clicker.client_action", ClientAction); diff --git a/awesome_clicker/static/src/client_action/client_action.xml b/awesome_clicker/static/src/client_action/client_action.xml new file mode 100644 index 00000000000..9869ac1e091 --- /dev/null +++ b/awesome_clicker/static/src/client_action/client_action.xml @@ -0,0 +1,84 @@ + + + +
+ Clicks: + + + +
+

Bots

+
+ +
+
+
+ x ( clicks/10 seconds) +
+
+ +
+
+
+
+
+
+
+

Power multiplier

+
+
+
+ x +
+
+ +
+
+
+
+
+ +
+

Trees

+
+ +
+
+
+ x (1x /30 seconds) +
+
+ +
+
+
+
+
+
+
+

Fruits

+
+ +
+
+
+ x +
+
+
+
+
+
+
+
+
+
+
diff --git a/awesome_clicker/static/src/form_controller/form_controller.js b/awesome_clicker/static/src/form_controller/form_controller.js new file mode 100644 index 00000000000..94431213cf2 --- /dev/null +++ b/awesome_clicker/static/src/form_controller/form_controller.js @@ -0,0 +1,15 @@ +import { FormController } from "@web/views/form/form_controller"; +import { patch } from "@web/core/utils/patch"; +import { useClicker } from "../clicker_hook"; + +const FormControllerPatch = { + setup() { + super.setup(...arguments); + if (Math.random < 0.01) { + const clicker = useClicker(); + clicker.giveReward(); + } + } +}; + +patch(FormController.prototype, FormControllerPatch); diff --git a/awesome_clicker/static/src/utils.js b/awesome_clicker/static/src/utils.js new file mode 100644 index 00000000000..f3f7c3977e9 --- /dev/null +++ b/awesome_clicker/static/src/utils.js @@ -0,0 +1,3 @@ +export function choose(list) { + return list[Math.floor(Math.random() * list.length)]; +} diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index a1cd72893d7..31406e8addb 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -12,7 +12,7 @@ 'author': "Odoo", 'website': "https://www.odoo.com/", - 'category': 'Tutorials', + 'category': 'Tutorials/AwesomeDashboard', 'version': '0.1', 'application': True, 'installable': True, diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js deleted file mode 100644 index c4fb245621b..00000000000 --- a/awesome_dashboard/static/src/dashboard.js +++ /dev/null @@ -1,8 +0,0 @@ -import { Component } from "@odoo/owl"; -import { registry } from "@web/core/registry"; - -class AwesomeDashboard extends Component { - static template = "awesome_dashboard.AwesomeDashboard"; -} - -registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml deleted file mode 100644 index 1a2ac9a2fed..00000000000 --- a/awesome_dashboard/static/src/dashboard.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - hello dashboard - - - diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js new file mode 100644 index 00000000000..e9eed021aed --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,90 @@ +/** @odoo-module **/ + +import { Component, useState } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { Layout } from "@web/search/layout"; +import { useService } from "@web/core/utils/hooks"; +import { DashboardItem } from "./dashboard_item"; +import { PieChart } from "./pie_chart/pie_chart"; +import { Dialog } from "@web/core/dialog/dialog"; +import { CheckBox } from "@web/core/checkbox/checkbox"; +import { browser } from "@web/core/browser/browser"; + +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem, PieChart }; + + setup() { + this.action = useService("action"); + this.statistics = useState(useService("awesome_dashboard.statistics")); + this.items = registry.category("awesome_dashboard").getAll(); + this.dialog = useService("dialog"); + this.state = useState({ + displayItems: browser.localStorage.getItem("displayDashboardItems")?.split(",") || [] + }); + + } + + openDialog() { + this.dialog.add(ConfigurationDialog, { + items: this.items, + displayItems: this.state.displayItems, + onUpdateConfiguration: this.updateConfiguration.bind(this), + }); + }; + + updateConfiguration(newDisplayItems) { + this.state.displayItems = newDisplayItems; + } + + openCustomerView() { + this.action.doAction("base.action_partner_form"); + } + + openLead() { + this.action.doAction({ + type: 'ir.actions.act_window', + name: 'Leads', + res_model: "crm.lead", + views: [[false, 'list'], [false, 'form']], + }); + } +} + +class ConfigurationDialog extends Component { + static template = "awesome_dashboard.ConfigurationDialog"; + static components = { Dialog, CheckBox }; + static props = { + items: Object, + displayItems: Object, + onUpdateConfiguration: Function, + close: Function, + } + + setup() { + this.items = useState(this.props.items.map((item) => { + return { + ...item, + enabled: this.props.displayItems.includes(item.id), + } + })); + } + + onChange(checked, changedItem){ + changedItem.enabled = checked; + const newDisplayItems = Object.values(this.items).filter( + (item) => item.enabled + ).map((item) => item.id); + + browser.localStorage.setItem("displayDashboardItems", newDisplayItems); + + this.props.onUpdateConfiguration(newDisplayItems); + } + + apply() { + this.props.close(); + } + +} + +registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard/dashboard.scss b/awesome_dashboard/static/src/dashboard/dashboard.scss new file mode 100644 index 00000000000..3a034d95f6d --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.scss @@ -0,0 +1,3 @@ +.o_dashboard { + background-color: rgb(237, 182, 245); +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..ff76f0d4bb1 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + +
+ + + + + + +
+
+
+ + + + Which cards do you whish to see ? + + + + + + + + + + + +
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item.js new file mode 100644 index 00000000000..dbd47f04452 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item.js @@ -0,0 +1,16 @@ + +import { Component } from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.DashboardItem"; + static props = { + size: { + type: Number, + optional: true, + } + } + static defaultProps = { + size: 1, + } + +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item.xml new file mode 100644 index 00000000000..a0ba8116392 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item.xml @@ -0,0 +1,12 @@ + + + + +
+
+ +
+
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_items.js b/awesome_dashboard/static/src/dashboard/dashboard_items.js new file mode 100644 index 00000000000..05a4602604f --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,65 @@ +import { NumberCard } from "./number_card/number_card"; +import { PieChartCard } from "./pie_chart_card/pie_chart_card"; +import { registry } from "@web/core/registry"; + +const items = [ + { + id: "average_quantity", + description: "Average amount of t-shirt", + Component: NumberCard, + props: (data) => ({ + title: "Average amount of t-shirt by order this month", + value: data.average_quantity, + }) + }, + { + id: "average_time", + description: "Average time for an order", + Component: NumberCard, + props: (data) => ({ + title: "Average time for an order to go from 'new' to 'sent' or 'cancelled'", + value: data.average_time, + }) + }, + { + id: "number_new_orders", + description: "New orders this month", + Component: NumberCard, + props: (data) => ({ + title: "Number of new orders this month", + value: data.nb_new_orders, + }) + }, + { + id: "cancelled_orders", + description: "Cancelled orders this month", + Component: NumberCard, + props: (data) => ({ + title: "Number of cancelled orders this month", + value: data.nb_cancelled_orders, + }) + }, + { + id: "amount_new_orders", + description: "amount orders this month", + Component: NumberCard, + props: (data) => ({ + title: "Total amount of new orders this month", + value: data.total_amount, + }) + }, + { + id: "pie_chart", + description: "Shirt orders by size", + Component: PieChartCard, + size: 2, + props: (data) => ({ + title: "Shirt orders by size", + data: data.orders_by_size, + }) + } +]; + +items.forEach(item => { + registry.category("awesome_dashboard").add(item.id, item); +}); diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.js b/awesome_dashboard/static/src/dashboard/number_card/number_card.js new file mode 100644 index 00000000000..cbe5f97d5c8 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.js @@ -0,0 +1,10 @@ +import { Component } from "@odoo/owl"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.NumberCard"; + static props = { + title: String, + value: Number, + }; + +} diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.xml b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml new file mode 100644 index 00000000000..51e5bc07a26 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml @@ -0,0 +1,9 @@ + + + + +
+ +
+
+
diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js new file mode 100644 index 00000000000..8646af2b2a7 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js @@ -0,0 +1,41 @@ +import { Component, onWillStart, useRef, onMounted, onWillUnmount } from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; +import { getColor } from "@web/core/colors/colors"; + +export class PieChart extends Component { + static template = "awesome_dashboard.PieChart"; + static props = { + label: String, + data: Object, + }; + + setup() { + this.canvasRef = useRef("canvas"); + onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js")); + onMounted(() => { + this.displayChart(); + }); + onWillUnmount(() => { + this.chart.destroy(); + }); + } + + displayChart() { + const labels = Object.keys(this.props.data); + const data = Object.values(this.props.data); + const color = labels.map((_, index) => getColor(index)); + this.chart = new Chart(this.canvasRef.el, { + type: "pie", + data: { + labels: labels, + datasets: [ + { + label: this.props.label, + data: data, + backgroundColor: color, + }, + ], + }, + }); + } +} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml new file mode 100644 index 00000000000..14e6684262c --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml @@ -0,0 +1,10 @@ + + + +
+
+ +
+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js new file mode 100644 index 00000000000..dbd169dda76 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js @@ -0,0 +1,11 @@ +import { Component } from "@odoo/owl"; +import { PieChart } from "../pie_chart/pie_chart"; + +export class PieChartCard extends Component { + static template = "awesome_dashboard.PieChartCard"; + static components = { PieChart }; + static props = { + title: String, + data: Object, + }; +} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml new file mode 100644 index 00000000000..c3177d2672b --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/awesome_dashboard/static/src/dashboard/statistics_service.js b/awesome_dashboard/static/src/dashboard/statistics_service.js new file mode 100644 index 00000000000..bb71291da95 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/statistics_service.js @@ -0,0 +1,21 @@ +import { rpc } from "@web/core/network/rpc"; +import { reactive } from "@odoo/owl"; +import { registry } from "@web/core/registry"; + +const statisticsService = { + start() { + const statistics = reactive({ isReady: false }); + + async function loadStatistics() { + const updates = await rpc("/awesome_dashboard/statistics"); + Object.assign(statistics, updates, { isReady: true }); + } + + setInterval(loadStatistics, 10*60*1000); + loadStatistics(); + + return statistics; + }, +}; + +registry.category("services").add("awesome_dashboard.statistics", statisticsService); diff --git a/awesome_dashboard/static/src/dashboard_loader.js b/awesome_dashboard/static/src/dashboard_loader.js new file mode 100644 index 00000000000..fe69763e354 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_loader.js @@ -0,0 +1,12 @@ +import { Component, xml } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { LazyComponent } from "@web/core/assets"; + +export class DashboardLoader extends Component { + static components = { LazyComponent }; + static template = xml` + + `; +} + +registry.category("actions").add("awesome_dashboard.dashboard", DashboardLoader); diff --git a/awesome_gallery/__manifest__.py b/awesome_gallery/__manifest__.py index f0fe026a9c6..624766dca89 100644 --- a/awesome_gallery/__manifest__.py +++ b/awesome_gallery/__manifest__.py @@ -11,7 +11,7 @@ 'version': '0.1', 'application': True, - 'category': 'Tutorials', + 'category': 'Tutorials/AwesomeGallery', 'installable': True, 'depends': ['web', 'contacts'], 'data': [ @@ -22,6 +22,5 @@ 'awesome_gallery/static/src/**/*', ], }, - 'author': 'Odoo S.A.', 'license': 'AGPL-3' } diff --git a/awesome_gallery/models/ir_ui_view.py b/awesome_gallery/models/ir_ui_view.py index 0c11b8298ac..429ec33a4d8 100644 --- a/awesome_gallery/models/ir_ui_view.py +++ b/awesome_gallery/models/ir_ui_view.py @@ -6,3 +6,9 @@ class View(models.Model): _inherit = 'ir.ui.view' type = fields.Selection(selection_add=[('gallery', "Awesome Gallery")]) + + def _is_qweb_based_view(self, view_type): + return view_type == "gallery" or super()._is_qweb_based_view(view_type) + + def _get_view_info(self): + return {'gallery': {'icon': 'fa fa-picture-o'}} | super()._get_view_info() diff --git a/awesome_gallery/static/src/gallery_arch_parser.js b/awesome_gallery/static/src/gallery_arch_parser.js new file mode 100644 index 00000000000..e25b343998d --- /dev/null +++ b/awesome_gallery/static/src/gallery_arch_parser.js @@ -0,0 +1,13 @@ + + +export class GalleryArchParser { + parse(xmlDoc) { + const imageField = xmlDoc.getAttribute("image_field"); + const limit = xmlDoc.getAttribute("limit") || 80; + return { + imageField, + limit, + }; + } +} + diff --git a/awesome_gallery/static/src/gallery_controller.js b/awesome_gallery/static/src/gallery_controller.js new file mode 100644 index 00000000000..7a3895aacd5 --- /dev/null +++ b/awesome_gallery/static/src/gallery_controller.js @@ -0,0 +1,41 @@ +import { Component, onWillStart, onWillUpdateProps, useState } from "@odoo/owl"; +import { standardViewProps } from "@web/views/standard_view_props"; +import { Layout } from "@web/search/layout"; +import { useService } from "@web/core/utils/hooks"; + +export class GalleryController extends Component { + static template = "awesome_gallery.GalleryController"; + static props = { + ...standardViewProps, + archInfo: Object, + }; + static components = { Layout }; + + setup() { + this.orm = useService("orm"); + this.images = useState({ data: [] }); + onWillStart(async () => { + const { records } = await this.loadImages(this.props.domain); + this.images.data = records; + }); + + onWillUpdateProps(async (nextProps) => { + if (JSON.stringify(nextProps.domain) !== JSON.stringify(this.props.domain)) { + const { records } = await this.loadImages(nextProps.domain); + this.images.data = records; + } + }) + } + + loadImages(domain) { + return this.orm.webSearchRead(this.props.resModel, domain, { + limit: this.props.archInfo.limit, + specification: { + [this.props.archInfo.imageField]: {}, + }, + context: { + bin_size: true, + } + }); + } +} diff --git a/awesome_gallery/static/src/gallery_controller.xml b/awesome_gallery/static/src/gallery_controller.xml new file mode 100644 index 00000000000..5c14bcc498b --- /dev/null +++ b/awesome_gallery/static/src/gallery_controller.xml @@ -0,0 +1,12 @@ + + + + +

+ id: + bin_size: +

+
+
+
+
diff --git a/awesome_gallery/static/src/gallery_view.js b/awesome_gallery/static/src/gallery_view.js new file mode 100644 index 00000000000..7772e43a677 --- /dev/null +++ b/awesome_gallery/static/src/gallery_view.js @@ -0,0 +1,27 @@ +/** @odoo-module */ + +import { registry } from "@web/core/registry" +import { GalleryController } from "./gallery_controller"; +import { GalleryArchParser } from "./gallery_arch_parser"; + +export const galleryView = { + Type: "gallery", + display_name: "Awesome Gallery", + icon: "fa fa-picture-o", + multiRecord: true, + Controller: GalleryController, + ArchParser: GalleryArchParser, + + props(genericProps, view) { + const { ArchParser } = view; + const { arch } = genericProps; + const archInfo = new ArchParser().parse(arch); + + return { + ...genericProps, + archInfo, + }; + }, +}; + +registry.category("views").add("gallery", galleryView); diff --git a/awesome_gallery/views/views.xml b/awesome_gallery/views/views.xml index 56327365875..7f6066c0448 100644 --- a/awesome_gallery/views/views.xml +++ b/awesome_gallery/views/views.xml @@ -1,10 +1,17 @@ + + awesome_gallery.orders.gallery + res.partner + + + + Contacts res.partner - kanban,tree,form,activity + kanban,list,form,activity,gallery {'default_is_company': True} diff --git a/awesome_kanban/__manifest__.py b/awesome_kanban/__manifest__.py index 6f31bc8de0d..affef78bb12 100644 --- a/awesome_kanban/__manifest__.py +++ b/awesome_kanban/__manifest__.py @@ -11,7 +11,7 @@ 'version': '0.1', 'application': True, - 'category': 'Tutorials', + 'category': 'Tutorials/AwesomeKanban', 'installable': True, 'depends': ['web', 'crm'], 'data': [ @@ -22,6 +22,5 @@ 'awesome_kanban/static/src/**/*', ], }, - 'author': 'Odoo S.A.', 'license': 'AGPL-3' } diff --git a/awesome_kanban/static/src/awesome_kanban_view.js b/awesome_kanban/static/src/awesome_kanban_view.js index 0da52b22c9d..9f33fc1300b 100644 --- a/awesome_kanban/static/src/awesome_kanban_view.js +++ b/awesome_kanban/static/src/awesome_kanban_view.js @@ -1 +1,3 @@ +/** @odoo-module */ + // TODO: Define here your AwesomeKanban view diff --git a/awesome_owl/__manifest__.py b/awesome_owl/__manifest__.py index e8ac1cda552..77abad510ef 100644 --- a/awesome_owl/__manifest__.py +++ b/awesome_owl/__manifest__.py @@ -16,7 +16,7 @@ # Categories can be used to filter modules in modules listing # Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml # for the full list - 'category': 'Tutorials', + 'category': 'Tutorials/AwesomeOwl', 'version': '0.1', # any module necessary for this one to work correctly @@ -31,6 +31,7 @@ ('include', 'web._assets_helpers'), 'web/static/src/scss/pre_variables.scss', 'web/static/lib/bootstrap/scss/_variables.scss', + 'web/static/lib/bootstrap/scss/_maps.scss', ('include', 'web._assets_bootstrap'), ('include', 'web._assets_core'), 'web/static/src/libs/fontawesome/css/font-awesome.css', diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..f5429c872a1 --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,18 @@ +import { Component, useState } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.card"; + + static props = { + title: String, + slots: Object, + } + + setup() { + this.showContent = useState({ value: true }); + } + + toggleContent() { + this.showContent.value = !this.showContent.value; + } +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..426af914910 --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,17 @@ + + + + +
+
+
+ + +
+
+ +
+
+
+
+
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..625206e5d8c --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,23 @@ +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.counter"; + + static props = { + onChange: { + type: Function, + optional: true, + } + }; + + setup() { + this.state = useState({ value: 0 }); + } + + increment() { + this.state.value++; + if (this.props.onChange) { + this.props.onChange(); + } + } +} diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml new file mode 100644 index 00000000000..acead7ab7d7 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,11 @@ + + + + +
+

Counter:

+ +
+
+ +
diff --git a/awesome_owl/static/src/main.js b/awesome_owl/static/src/main.js index 1aaea902b55..1af6c827e0b 100644 --- a/awesome_owl/static/src/main.js +++ b/awesome_owl/static/src/main.js @@ -4,9 +4,8 @@ import { Playground } from "./playground"; const config = { dev: true, - name: "Owl Tutorial" + name: "Owl Tutorial", }; // Mount the Playground component when the document.body is ready whenReady(() => mountComponent(Playground, document.body, config)); - diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..412cbc7cae2 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,27 @@ -import { Component } from "@odoo/owl"; +/** @odoo-module **/ + +import { Component, markup, useState } from "@odoo/owl"; +import { Counter } from './counter/counter'; +import { Card } from './card/card'; +import { TodoList } from "./todo/todo_list"; export class Playground extends Component { static template = "awesome_owl.playground"; + + static components = { Counter, Card, TodoList }; + + setup() { + this.sum = useState({ value: 0 }); + } + + title1 = 'Title 1'; + title2 = 'Title 2'; + + content1 = "
Some content
"; + content2 = markup("
Some content
"); + + incrementSum() { + this.sum.value++; + } + } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..4ea491072ee 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -3,7 +3,22 @@
- hello world + Hello World ! +
+ + + +
+ + + + +
+
+

The sum is:

+
+
+
diff --git a/awesome_owl/static/src/todo/todo_item.js b/awesome_owl/static/src/todo/todo_item.js new file mode 100644 index 00000000000..0433f66555a --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.js @@ -0,0 +1,33 @@ +import { Component, useState } from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.TodoItem" + + static props = { + todo: { + type: Object, + shape: { + id: Number, + description: String, + isCompleted: Boolean, + } + }, + toggleState: { + type: Function, + optional: true, + }, + removeTodo: { + type: Function, + optional: true, + } + }; + + onChange() { + this.props.toggleState(this.props.todo.id); + } + + removeTodo() { + this.props.removeTodo(this.props.todo.id); + } + +} diff --git a/awesome_owl/static/src/todo/todo_item.xml b/awesome_owl/static/src/todo/todo_item.xml new file mode 100644 index 00000000000..4e6a80edb42 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.xml @@ -0,0 +1,14 @@ + + + + +
+

+ . + + +

+
+
+ +
diff --git a/awesome_owl/static/src/todo/todo_list.js b/awesome_owl/static/src/todo/todo_list.js new file mode 100644 index 00000000000..3f48f1f1b5d --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.js @@ -0,0 +1,49 @@ +import { Component, useState, useRef, onMounted } from "@odoo/owl"; +import { TodoItem } from "./todo_item"; + +export class TodoList extends Component { + static template = "awesome_owl.TodoList" + + setup() { + this.todos = useState([ { + id : 0, + description : "Buy milk", + isCompleted : false, + } ]); + this.inputRef = useRef('input_todo') + onMounted(() => { + this.inputRef.el.focus(); + }) + } + + static components = { TodoItem }; + + addTodo(ev) { + if (ev.keyCode == 13 && ev.target.value != "") { + this.todos.push({ id: this.todos.length, description: ev.target.value, isCompleted: false}); + ev.target.value = ""; + } + + } + + toggleState(id) { + const todo = this.todos.find((todo) => todo.id == id); + if (todo) { + todo.isCompleted = !todo.isCompleted; + } + } + + removeTodo(id) { + let index = this.todos.findIndex((todo) => todo.id === id); + if (index >= 0) { + this.todos.splice(index, 1); + this.todos.forEach(todo => { + if (index < todo.id) { + todo.id = index; + index++; + } + }); + } + } + +} diff --git a/awesome_owl/static/src/todo/todo_list.xml b/awesome_owl/static/src/todo/todo_list.xml new file mode 100644 index 00000000000..8f307542496 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.xml @@ -0,0 +1,14 @@ + + + + +
+ +

To do list :

+

+ +

+
+
+ +